From 348b8c44142ce14a8395552f8d429fb9d0b24a36 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 26 Mar 2015 12:58:02 -0400 Subject: [PATCH 01/52] use server to build initial stream url's --- MediaBrowser.Api/Playback/MediaInfoService.cs | 146 ++++++++++++++++-- .../Channels/ChannelMediaInfo.cs | 3 +- MediaBrowser.Controller/Entities/Video.cs | 3 +- .../MediaBrowser.Model.Portable.csproj | 9 +- .../MediaBrowser.Model.net35.csproj | 9 +- MediaBrowser.Model/ApiClient/IApiClient.cs | 2 +- MediaBrowser.Model/Dlna/StreamBuilder.cs | 8 +- MediaBrowser.Model/Dlna/StreamInfo.cs | 8 +- MediaBrowser.Model/Dto/MediaSourceInfo.cs | 8 +- MediaBrowser.Model/MediaBrowser.Model.csproj | 3 +- .../MediaInfo/PlaybackInfoRequest.cs | 9 ++ ...aInfoResult.cs => PlaybackInfoResponse.cs} | 4 +- 12 files changed, 178 insertions(+), 34 deletions(-) create mode 100644 MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs rename MediaBrowser.Model/MediaInfo/{LiveMediaInfoResult.cs => PlaybackInfoResponse.cs} (91%) diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs index 2a17b2fe8f..379b69710a 100644 --- a/MediaBrowser.Api/Playback/MediaInfoService.cs +++ b/MediaBrowser.Api/Playback/MediaInfoService.cs @@ -1,8 +1,11 @@ -using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Devices; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dto; using MediaBrowser.Model.MediaInfo; +using MediaBrowser.Model.Session; using ServiceStack; using System; using System.Collections.Generic; @@ -13,7 +16,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Api.Playback { [Route("/Items/{Id}/MediaInfo", "GET", Summary = "Gets live playback media info for an item")] - public class GetLiveMediaInfo : IReturn + public class GetLiveMediaInfo : IReturn { [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public string Id { get; set; } @@ -23,7 +26,17 @@ namespace MediaBrowser.Api.Playback } [Route("/Items/{Id}/PlaybackInfo", "GET", Summary = "Gets live playback media info for an item")] - public class GetPlaybackInfo : IReturn + public class GetPlaybackInfo : IReturn + { + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Id { get; set; } + + [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] + public string UserId { get; set; } + } + + [Route("/Items/{Id}/PlaybackInfo", "POST", Summary = "Gets live playback media info for an item")] + public class GetPostedPlaybackInfo : PlaybackInfoRequest, IReturn { [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public string Id { get; set; } @@ -36,26 +49,55 @@ namespace MediaBrowser.Api.Playback public class MediaInfoService : BaseApiService { private readonly IMediaSourceManager _mediaSourceManager; + private readonly IDeviceManager _deviceManager; + private readonly ILibraryManager _libraryManager; - public MediaInfoService(IMediaSourceManager mediaSourceManager) + public MediaInfoService(IMediaSourceManager mediaSourceManager, IDeviceManager deviceManager, ILibraryManager libraryManager) { _mediaSourceManager = mediaSourceManager; + _deviceManager = deviceManager; + _libraryManager = libraryManager; + } + + public async Task Get(GetPlaybackInfo request) + { + var result = await GetPlaybackInfo(request.Id, request.UserId).ConfigureAwait(false); + return ToOptimizedResult(result); } - public Task Get(GetPlaybackInfo request) + public async Task Get(GetLiveMediaInfo request) { - return GetPlaybackInfo(request.Id, request.UserId); + var result = await GetPlaybackInfo(request.Id, request.UserId).ConfigureAwait(false); + return ToOptimizedResult(result); } - public Task Get(GetLiveMediaInfo request) + public async Task Post(GetPostedPlaybackInfo request) { - return GetPlaybackInfo(request.Id, request.UserId); + var info = await GetPlaybackInfo(request.Id, request.UserId).ConfigureAwait(false); + var authInfo = AuthorizationContext.GetAuthorizationInfo(Request); + + var profile = request.DeviceProfile; + //if (profile == null) + //{ + // var caps = _deviceManager.GetCapabilities(authInfo.DeviceId); + // if (caps != null) + // { + // profile = caps.DeviceProfile; + // } + //} + + if (profile != null) + { + SetDeviceSpecificData(request.Id, info, profile, authInfo, null); + } + + return ToOptimizedResult(info); } - private async Task GetPlaybackInfo(string id, string userId) + private async Task GetPlaybackInfo(string id, string userId) { IEnumerable mediaSources; - var result = new LiveMediaInfoResult(); + var result = new PlaybackInfoResponse(); try { @@ -68,9 +110,89 @@ namespace MediaBrowser.Api.Playback } result.MediaSources = mediaSources.ToList(); - result.StreamId = Guid.NewGuid().ToString("N"); - return ToOptimizedResult(result); + if (result.MediaSources.Count == 0) + { + if (!result.ErrorCode.HasValue) + { + result.ErrorCode = PlaybackErrorCode.NoCompatibleStream; + } + } + else + { + result.StreamId = Guid.NewGuid().ToString("N"); + } + + return result; + } + + private void SetDeviceSpecificData(string itemId, PlaybackInfoResponse result, DeviceProfile profile, AuthorizationInfo auth, int? maxBitrate) + { + var streamBuilder = new StreamBuilder(); + + var item = _libraryManager.GetItemById(itemId); + + foreach (var mediaSource in result.MediaSources) + { + var options = new VideoOptions + { + MediaSources = new List { mediaSource }, + Context = EncodingContext.Streaming, + DeviceId = auth.DeviceId, + ItemId = item.Id.ToString("N"), + Profile = profile, + MaxBitrate = maxBitrate + }; + + if (mediaSource.SupportsDirectPlay) + { + var supportsDirectStream = mediaSource.SupportsDirectStream; + + // Dummy this up to fool StreamBuilder + mediaSource.SupportsDirectStream = true; + + // The MediaSource supports direct stream, now test to see if the client supports it + var streamInfo = item is Video ? + streamBuilder.BuildVideoItem(options) : + streamBuilder.BuildAudioItem(options); + + if (streamInfo == null || !streamInfo.IsDirectStream) + { + mediaSource.SupportsDirectPlay = false; + } + + // Set this back to what it was + mediaSource.SupportsDirectStream = supportsDirectStream; + } + + if (mediaSource.SupportsDirectStream) + { + // The MediaSource supports direct stream, now test to see if the client supports it + var streamInfo = item is Video ? + streamBuilder.BuildVideoItem(options) : + streamBuilder.BuildAudioItem(options); + + if (streamInfo == null || !streamInfo.IsDirectStream) + { + mediaSource.SupportsDirectStream = false; + } + } + + if (mediaSource.SupportsTranscoding) + { + // The MediaSource supports direct stream, now test to see if the client supports it + var streamInfo = item is Video ? + streamBuilder.BuildVideoItem(options) : + streamBuilder.BuildAudioItem(options); + + if (streamInfo != null && streamInfo.PlayMethod == PlayMethod.Transcode) + { + mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).Substring(1); + mediaSource.TranscodingContainer = streamInfo.Container; + mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol; + } + } + } } } } diff --git a/MediaBrowser.Controller/Channels/ChannelMediaInfo.cs b/MediaBrowser.Controller/Channels/ChannelMediaInfo.cs index 0c2e30923c..94fa1ac02f 100644 --- a/MediaBrowser.Controller/Channels/ChannelMediaInfo.cs +++ b/MediaBrowser.Controller/Channels/ChannelMediaInfo.cs @@ -62,7 +62,8 @@ namespace MediaBrowser.Controller.Channels RunTimeTicks = RunTimeTicks, Name = id, Id = id, - ReadAtNativeFramerate = ReadAtNativeFramerate + ReadAtNativeFramerate = ReadAtNativeFramerate, + SupportsDirectStream = Protocol == MediaProtocol.File || Protocol == MediaProtocol.Http }; var bitrate = (AudioBitrate ?? 0) + (VideoBitrate ?? 0); diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index ba84beca34..00dc5dc672 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -501,7 +501,8 @@ namespace MediaBrowser.Controller.Entities Formats = (i.FormatName ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(), Timestamp = i.Timestamp, Type = type, - PlayableStreamFileNames = i.PlayableStreamFileNames.ToList() + PlayableStreamFileNames = i.PlayableStreamFileNames.ToList(), + SupportsDirectStream = i.VideoType == VideoType.VideoFile }; if (i.IsShortcut) diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj index c2d46b9b3f..118e5659ad 100644 --- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj +++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj @@ -800,12 +800,15 @@ MediaInfo\IBlurayExaminer.cs - - MediaInfo\LiveMediaInfoResult.cs - MediaInfo\MediaProtocol.cs + + MediaInfo\PlaybackInfoRequest.cs + + + MediaInfo\PlaybackInfoResponse.cs + MediaInfo\SubtitleFormat.cs diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj index 52745b5dee..a97bf2a8c6 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -756,12 +756,15 @@ MediaInfo\IBlurayExaminer.cs - - MediaInfo\LiveMediaInfoResult.cs - MediaInfo\MediaProtocol.cs + + MediaInfo\PlaybackInfoRequest.cs + + + MediaInfo\PlaybackInfoResponse.cs + MediaInfo\SubtitleFormat.cs diff --git a/MediaBrowser.Model/ApiClient/IApiClient.cs b/MediaBrowser.Model/ApiClient/IApiClient.cs index 14cdbdc9ab..19d6acf339 100644 --- a/MediaBrowser.Model/ApiClient/IApiClient.cs +++ b/MediaBrowser.Model/ApiClient/IApiClient.cs @@ -251,7 +251,7 @@ namespace MediaBrowser.Model.ApiClient /// The item identifier. /// The user identifier. /// Task<LiveMediaInfoResult>. - Task GetPlaybackInfo(string itemId, string userId); + Task GetPlaybackInfo(string itemId, string userId); /// /// Gets the users async. diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 3f88f9ac06..7338f604c0 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -118,9 +118,7 @@ namespace MediaBrowser.Model.Dlna return stream; } - PlaybackException error = new PlaybackException(); - error.ErrorCode = PlaybackErrorCode.NoCompatibleStream; - throw error; + return null; } private StreamInfo BuildAudioItem(MediaSourceInfo item, AudioOptions options) @@ -221,7 +219,7 @@ namespace MediaBrowser.Model.Dlna playlistItem.EstimateContentLength = transcodingProfile.EstimateContentLength; playlistItem.Container = transcodingProfile.Container; playlistItem.AudioCodec = transcodingProfile.AudioCodec; - playlistItem.Protocol = transcodingProfile.Protocol; + playlistItem.SubProtocol = transcodingProfile.Protocol; List audioCodecProfiles = new List(); foreach (CodecProfile i in options.Profile.CodecProfiles) @@ -374,7 +372,7 @@ namespace MediaBrowser.Model.Dlna playlistItem.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo; playlistItem.AudioCodec = transcodingProfile.AudioCodec.Split(',')[0]; playlistItem.VideoCodec = transcodingProfile.VideoCodec; - playlistItem.Protocol = transcodingProfile.Protocol; + playlistItem.SubProtocol = transcodingProfile.Protocol; playlistItem.AudioStreamIndex = audioStreamIndex; List videoTranscodingConditions = new List(); diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index 4eb1c0a8ef..63ddd07cd7 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -24,7 +24,7 @@ namespace MediaBrowser.Model.Dlna public string Container { get; set; } - public string Protocol { get; set; } + public string SubProtocol { get; set; } public long StartPositionTicks { get; set; } @@ -69,7 +69,7 @@ namespace MediaBrowser.Model.Dlna public SubtitleDeliveryMethod SubtitleDeliveryMethod { get; set; } public string SubtitleFormat { get; set; } - public LiveMediaInfoResult PlaybackInfo { get; set; } + public PlaybackInfoResponse PlaybackInfo { get; set; } public string MediaSourceId { @@ -115,7 +115,7 @@ namespace MediaBrowser.Model.Dlna return string.Format("{0}/audio/{1}/stream{2}?{3}", baseUrl, ItemId, extension, dlnaCommand); } - if (StringHelper.EqualsIgnoreCase(Protocol, "hls")) + if (StringHelper.EqualsIgnoreCase(SubProtocol, "hls")) { return string.Format("{0}/videos/{1}/master.m3u8?{2}", baseUrl, ItemId, dlnaCommand); } @@ -207,7 +207,7 @@ namespace MediaBrowser.Model.Dlna List list = new List(); // HLS will preserve timestamps so we can just grab the full subtitle stream - long startPositionTicks = StringHelper.EqualsIgnoreCase(Protocol, "hls") + long startPositionTicks = StringHelper.EqualsIgnoreCase(SubProtocol, "hls") ? 0 : StartPositionTicks; diff --git a/MediaBrowser.Model/Dto/MediaSourceInfo.cs b/MediaBrowser.Model/Dto/MediaSourceInfo.cs index 5495a1f692..31d310acda 100644 --- a/MediaBrowser.Model/Dto/MediaSourceInfo.cs +++ b/MediaBrowser.Model/Dto/MediaSourceInfo.cs @@ -24,6 +24,7 @@ namespace MediaBrowser.Model.Dto public bool ReadAtNativeFramerate { get; set; } public bool SupportsTranscoding { get; set; } public bool SupportsDirectStream { get; set; } + public bool SupportsDirectPlay { get; set; } public VideoType? VideoType { get; set; } @@ -39,7 +40,11 @@ namespace MediaBrowser.Model.Dto public int? Bitrate { get; set; } public TransportStreamTimestamp? Timestamp { get; set; } - public Dictionary RequiredHttpHeaders { get; set; } + public Dictionary RequiredHttpHeaders { get; set; } + + public string TranscodingUrl { get; set; } + public string TranscodingSubProtocol { get; set; } + public string TranscodingContainer { get; set; } public MediaSourceInfo() { @@ -49,6 +54,7 @@ namespace MediaBrowser.Model.Dto PlayableStreamFileNames = new List(); SupportsTranscoding = true; SupportsDirectStream = true; + SupportsDirectPlay = true; } public int? DefaultAudioStreamIndex { get; set; } diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 3a6eda6207..30eec434db 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -140,7 +140,8 @@ - + + diff --git a/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs b/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs new file mode 100644 index 0000000000..ffd4995ad9 --- /dev/null +++ b/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs @@ -0,0 +1,9 @@ +using MediaBrowser.Model.Dlna; + +namespace MediaBrowser.Model.MediaInfo +{ + public class PlaybackInfoRequest + { + public DeviceProfile DeviceProfile { get; set; } + } +} diff --git a/MediaBrowser.Model/MediaInfo/LiveMediaInfoResult.cs b/MediaBrowser.Model/MediaInfo/PlaybackInfoResponse.cs similarity index 91% rename from MediaBrowser.Model/MediaInfo/LiveMediaInfoResult.cs rename to MediaBrowser.Model/MediaInfo/PlaybackInfoResponse.cs index 16f4f76ee4..ed0824e8a4 100644 --- a/MediaBrowser.Model/MediaInfo/LiveMediaInfoResult.cs +++ b/MediaBrowser.Model/MediaInfo/PlaybackInfoResponse.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; namespace MediaBrowser.Model.MediaInfo { - public class LiveMediaInfoResult + public class PlaybackInfoResponse { /// /// Gets or sets the media sources. @@ -24,7 +24,7 @@ namespace MediaBrowser.Model.MediaInfo /// The error code. public PlaybackErrorCode? ErrorCode { get; set; } - public LiveMediaInfoResult() + public PlaybackInfoResponse() { MediaSources = new List(); } From 3ec7090c6e7d2f33524cc269f0c374cd6bb2ef8b Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 26 Mar 2015 14:24:13 -0400 Subject: [PATCH 02/52] fixes #1015 - Segmented transcoding --- MediaBrowser.Model/Dlna/StreamInfo.cs | 103 ++++++++++++++++-------- MediaBrowser.Model/Dto/NameValuePair.cs | 11 +++ 2 files changed, 81 insertions(+), 33 deletions(-) diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index 63ddd07cd7..46efd42310 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -89,7 +89,28 @@ namespace MediaBrowser.Model.Dlna public string ToUrl(string baseUrl, string accessToken) { - return ToDlnaUrl(baseUrl, accessToken); + if (PlayMethod == PlayMethod.DirectPlay) + { + return MediaSource.Path; + } + + if (string.IsNullOrEmpty(baseUrl)) + { + throw new ArgumentNullException(baseUrl); + } + + List list = new List(); + foreach (NameValuePair pair in BuildParams(this, accessToken)) + { + if (!string.IsNullOrEmpty(pair.Value)) + { + list.Add(string.Format("{0}={1}", pair.Name, pair.Value)); + } + } + + string queryString = string.Join("&", list.ToArray()); + + return GetUrl(baseUrl, queryString); } public string ToDlnaUrl(string baseUrl, string accessToken) @@ -99,62 +120,78 @@ namespace MediaBrowser.Model.Dlna return MediaSource.Path; } + string dlnaCommand = BuildDlnaParam(this, accessToken); + return GetUrl(baseUrl, dlnaCommand); + } + + private string GetUrl(string baseUrl, string queryString) + { if (string.IsNullOrEmpty(baseUrl)) { throw new ArgumentNullException(baseUrl); } - string dlnaCommand = BuildDlnaParam(this); - string extension = string.IsNullOrEmpty(Container) ? string.Empty : "." + Container; baseUrl = baseUrl.TrimEnd('/'); if (MediaType == DlnaProfileType.Audio) { - return string.Format("{0}/audio/{1}/stream{2}?{3}", baseUrl, ItemId, extension, dlnaCommand); + return string.Format("{0}/audio/{1}/stream{2}?{3}", baseUrl, ItemId, extension, queryString); } if (StringHelper.EqualsIgnoreCase(SubProtocol, "hls")) { - return string.Format("{0}/videos/{1}/master.m3u8?{2}", baseUrl, ItemId, dlnaCommand); + return string.Format("{0}/videos/{1}/master.m3u8?{2}", baseUrl, ItemId, queryString); } - return string.Format("{0}/videos/{1}/stream{2}?{3}", baseUrl, ItemId, extension, dlnaCommand); + return string.Format("{0}/videos/{1}/stream{2}?{3}", baseUrl, ItemId, extension, queryString); } - private static string BuildDlnaParam(StreamInfo item) + private static string BuildDlnaParam(StreamInfo item, string accessToken) { - List list = new List + List list = new List(); + + foreach (NameValuePair pair in BuildParams(item, accessToken)) { - item.DeviceProfileId ?? string.Empty, - item.DeviceId ?? string.Empty, - item.MediaSourceId ?? string.Empty, - (item.IsDirectStream).ToString().ToLower(), - item.VideoCodec ?? string.Empty, - item.AudioCodec ?? string.Empty, - item.AudioStreamIndex.HasValue ? StringHelper.ToStringCultureInvariant(item.AudioStreamIndex.Value) : string.Empty, - item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? StringHelper.ToStringCultureInvariant(item.SubtitleStreamIndex.Value) : string.Empty, - item.VideoBitrate.HasValue ? StringHelper.ToStringCultureInvariant(item.VideoBitrate.Value) : string.Empty, - item.AudioBitrate.HasValue ? StringHelper.ToStringCultureInvariant(item.AudioBitrate.Value) : string.Empty, - item.MaxAudioChannels.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxAudioChannels.Value) : string.Empty, - item.MaxFramerate.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxFramerate.Value) : string.Empty, - item.MaxWidth.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxWidth.Value) : string.Empty, - item.MaxHeight.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxHeight.Value) : string.Empty, - StringHelper.ToStringCultureInvariant(item.StartPositionTicks), - item.VideoLevel.HasValue ? StringHelper.ToStringCultureInvariant(item.VideoLevel.Value) : string.Empty - }; + list.Add(pair.Value); + } + + return string.Format("Params={0}", string.Join(";", list.ToArray())); + } - list.Add(item.IsDirectStream ? string.Empty : DateTime.UtcNow.Ticks.ToString(CultureInfo.InvariantCulture)); - list.Add(item.MaxRefFrames.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxRefFrames.Value) : string.Empty); - list.Add(item.MaxVideoBitDepth.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxVideoBitDepth.Value) : string.Empty); - list.Add(item.VideoProfile ?? string.Empty); - list.Add(item.Cabac.HasValue ? item.Cabac.Value.ToString() : string.Empty); + private static List BuildParams(StreamInfo item, string accessToken) + { + List list = new List(); + + list.Add(new NameValuePair("DeviceProfileId", item.DeviceProfileId ?? string.Empty)); + list.Add(new NameValuePair("DeviceId", item.DeviceId ?? string.Empty)); + list.Add(new NameValuePair("MediaSourceId", item.MediaSourceId ?? string.Empty)); + list.Add(new NameValuePair("Static", (item.IsDirectStream).ToString().ToLower())); + list.Add(new NameValuePair("VideoCodec", item.VideoCodec ?? string.Empty)); + list.Add(new NameValuePair("AudioCodec", item.AudioCodec ?? string.Empty)); + list.Add(new NameValuePair("AudioStreamIndex", item.AudioStreamIndex.HasValue ? StringHelper.ToStringCultureInvariant(item.AudioStreamIndex.Value) : string.Empty)); + list.Add(new NameValuePair("SubtitleStreamIndex", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? StringHelper.ToStringCultureInvariant(item.SubtitleStreamIndex.Value) : string.Empty)); + list.Add(new NameValuePair("VideoBitrate", item.VideoBitrate.HasValue ? StringHelper.ToStringCultureInvariant(item.VideoBitrate.Value) : string.Empty)); + list.Add(new NameValuePair("AudioBitrate", item.AudioBitrate.HasValue ? StringHelper.ToStringCultureInvariant(item.AudioBitrate.Value) : string.Empty)); + list.Add(new NameValuePair("MaxAudioChannels", item.MaxAudioChannels.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxAudioChannels.Value) : string.Empty)); + list.Add(new NameValuePair("MaxFramerate", item.MaxFramerate.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxFramerate.Value) : string.Empty)); + list.Add(new NameValuePair("MaxWidth", item.MaxWidth.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxWidth.Value) : string.Empty)); + list.Add(new NameValuePair("MaxHeight", item.MaxHeight.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxHeight.Value) : string.Empty)); + list.Add(new NameValuePair("StartTimeTicks", StringHelper.ToStringCultureInvariant(item.StartPositionTicks))); + list.Add(new NameValuePair("Level", item.VideoLevel.HasValue ? StringHelper.ToStringCultureInvariant(item.VideoLevel.Value) : string.Empty)); + + list.Add(new NameValuePair("ClientTime", item.IsDirectStream ? string.Empty : DateTime.UtcNow.Ticks.ToString(CultureInfo.InvariantCulture))); + list.Add(new NameValuePair("MaxRefFrames", item.MaxRefFrames.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxRefFrames.Value) : string.Empty)); + list.Add(new NameValuePair("MaxVideoBitDepth", item.MaxVideoBitDepth.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxVideoBitDepth.Value) : string.Empty)); + list.Add(new NameValuePair("Profile", item.VideoProfile ?? string.Empty)); + list.Add(new NameValuePair("Cabac", item.Cabac.HasValue ? item.Cabac.Value.ToString() : string.Empty)); string streamId = item.PlaybackInfo == null ? null : item.PlaybackInfo.StreamId; - list.Add(streamId ?? string.Empty); - - return string.Format("Params={0}", string.Join(";", list.ToArray())); + list.Add(new NameValuePair("StreamId", streamId ?? string.Empty)); + list.Add(new NameValuePair("api_key", accessToken ?? string.Empty)); + + return list; } public List GetExternalSubtitles(bool includeSelectedTrackOnly) diff --git a/MediaBrowser.Model/Dto/NameValuePair.cs b/MediaBrowser.Model/Dto/NameValuePair.cs index 2d55e8f2a1..a6e6879491 100644 --- a/MediaBrowser.Model/Dto/NameValuePair.cs +++ b/MediaBrowser.Model/Dto/NameValuePair.cs @@ -3,6 +3,17 @@ namespace MediaBrowser.Model.Dto { public class NameValuePair { + public NameValuePair() + { + + } + + public NameValuePair(string name, string value) + { + Name = name; + Value = value; + } + /// /// Gets or sets the name. /// From cd99ad43661a5353ba70b8f4b260e52b31efe737 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 26 Mar 2015 15:18:21 -0400 Subject: [PATCH 03/52] use server to build stream url's --- MediaBrowser.Api/Playback/MediaInfoService.cs | 60 +++++++++++++++---- MediaBrowser.Model/Dlna/StreamInfo.cs | 23 ++++++- .../MediaInfo/PlaybackInfoRequest.cs | 2 + 3 files changed, 70 insertions(+), 15 deletions(-) diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs index 379b69710a..ce140365bb 100644 --- a/MediaBrowser.Api/Playback/MediaInfoService.cs +++ b/MediaBrowser.Api/Playback/MediaInfoService.cs @@ -43,6 +43,15 @@ namespace MediaBrowser.Api.Playback [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] public string UserId { get; set; } + + [ApiMember(Name = "StartTimeTicks", Description = "Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public long? StartTimeTicks { get; set; } + + [ApiMember(Name = "AudioStreamIndex", Description = "Optional. The index of the audio stream to use. If omitted the first audio stream will be used.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int? AudioStreamIndex { get; set; } + + [ApiMember(Name = "SubtitleStreamIndex", Description = "Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int? SubtitleStreamIndex { get; set; } } [Authenticated] @@ -73,7 +82,7 @@ namespace MediaBrowser.Api.Playback public async Task Post(GetPostedPlaybackInfo request) { - var info = await GetPlaybackInfo(request.Id, request.UserId).ConfigureAwait(false); + var info = await GetPlaybackInfo(request.Id, request.UserId, request.MediaSource).ConfigureAwait(false); var authInfo = AuthorizationContext.GetAuthorizationInfo(Request); var profile = request.DeviceProfile; @@ -88,29 +97,38 @@ namespace MediaBrowser.Api.Playback if (profile != null) { - SetDeviceSpecificData(request.Id, info, profile, authInfo, null); + var mediaSourceId = request.MediaSource == null ? null : request.MediaSource.Id; + SetDeviceSpecificData(request.Id, info, profile, authInfo, null, request.StartTimeTicks ?? 0, mediaSourceId, request.AudioStreamIndex, request.SubtitleStreamIndex); } return ToOptimizedResult(info); } - private async Task GetPlaybackInfo(string id, string userId) + private async Task GetPlaybackInfo(string id, string userId, MediaSourceInfo mediaSource = null) { - IEnumerable mediaSources; var result = new PlaybackInfoResponse(); - try + if (mediaSource == null) { - mediaSources = await _mediaSourceManager.GetPlayackMediaSources(id, userId, true, CancellationToken.None).ConfigureAwait(false); + IEnumerable mediaSources; + + try + { + mediaSources = await _mediaSourceManager.GetPlayackMediaSources(id, userId, true, CancellationToken.None).ConfigureAwait(false); + } + catch (PlaybackException ex) + { + mediaSources = new List(); + result.ErrorCode = ex.ErrorCode; + } + + result.MediaSources = mediaSources.ToList(); } - catch (PlaybackException ex) + else { - mediaSources = new List(); - result.ErrorCode = ex.ErrorCode; + result.MediaSources = new List { mediaSource }; } - result.MediaSources = mediaSources.ToList(); - if (result.MediaSources.Count == 0) { if (!result.ErrorCode.HasValue) @@ -126,7 +144,15 @@ namespace MediaBrowser.Api.Playback return result; } - private void SetDeviceSpecificData(string itemId, PlaybackInfoResponse result, DeviceProfile profile, AuthorizationInfo auth, int? maxBitrate) + private void SetDeviceSpecificData(string itemId, + PlaybackInfoResponse result, + DeviceProfile profile, + AuthorizationInfo auth, + int? maxBitrate, + long startTimeTicks, + string mediaSourceId, + int? audioStreamIndex, + int? subtitleStreamIndex) { var streamBuilder = new StreamBuilder(); @@ -144,13 +170,20 @@ namespace MediaBrowser.Api.Playback MaxBitrate = maxBitrate }; + if (string.Equals(mediaSourceId, mediaSource.Id, StringComparison.OrdinalIgnoreCase)) + { + options.MediaSourceId = mediaSourceId; + options.AudioStreamIndex = audioStreamIndex; + options.SubtitleStreamIndex = subtitleStreamIndex; + } + if (mediaSource.SupportsDirectPlay) { var supportsDirectStream = mediaSource.SupportsDirectStream; // Dummy this up to fool StreamBuilder mediaSource.SupportsDirectStream = true; - + // The MediaSource supports direct stream, now test to see if the client supports it var streamInfo = item is Video ? streamBuilder.BuildVideoItem(options) : @@ -187,6 +220,7 @@ namespace MediaBrowser.Api.Playback if (streamInfo != null && streamInfo.PlayMethod == PlayMethod.Transcode) { + streamInfo.StartPositionTicks = startTimeTicks; mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).Substring(1); mediaSource.TranscodingContainer = streamInfo.Container; mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol; diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index 46efd42310..60615d1da9 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -102,10 +102,29 @@ namespace MediaBrowser.Model.Dlna List list = new List(); foreach (NameValuePair pair in BuildParams(this, accessToken)) { - if (!string.IsNullOrEmpty(pair.Value)) + if (string.IsNullOrEmpty(pair.Value)) { - list.Add(string.Format("{0}={1}", pair.Name, pair.Value)); + continue; } + + // Try to keep the url clean by omitting defaults + if (StringHelper.EqualsIgnoreCase(pair.Name, "StartTimeTicks") && + StringHelper.EqualsIgnoreCase(pair.Value, "0")) + { + continue; + } + if (StringHelper.EqualsIgnoreCase(pair.Name, "SubtitleStreamIndex") && + StringHelper.EqualsIgnoreCase(pair.Value, "-1")) + { + continue; + } + if (StringHelper.EqualsIgnoreCase(pair.Name, "Static") && + StringHelper.EqualsIgnoreCase(pair.Value, "false")) + { + continue; + } + + list.Add(string.Format("{0}={1}", pair.Name, pair.Value)); } string queryString = string.Join("&", list.ToArray()); diff --git a/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs b/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs index ffd4995ad9..783fb41203 100644 --- a/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs +++ b/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs @@ -1,9 +1,11 @@ using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Dto; namespace MediaBrowser.Model.MediaInfo { public class PlaybackInfoRequest { public DeviceProfile DeviceProfile { get; set; } + public MediaSourceInfo MediaSource { get; set; } } } From 8921e152ea617904fb4b6c4fe03fc8070fd03986 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 26 Mar 2015 15:21:40 -0400 Subject: [PATCH 04/52] removed dead code --- MediaBrowser.Server.Implementations/Sync/CloudSyncProfile.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Server.Implementations/Sync/CloudSyncProfile.cs b/MediaBrowser.Server.Implementations/Sync/CloudSyncProfile.cs index e13042538c..8d4c40fdc5 100644 --- a/MediaBrowser.Server.Implementations/Sync/CloudSyncProfile.cs +++ b/MediaBrowser.Server.Implementations/Sync/CloudSyncProfile.cs @@ -75,7 +75,7 @@ namespace MediaBrowser.Server.Implementations.Sync { Condition = ProfileConditionType.LessThanEqual, Property = ProfileConditionValue.RefFrames, - Value = "12", + Value = "4", IsRequired = false } } From 7e312e75bbb1324748319b29c9353716ed93a8da Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 26 Mar 2015 16:31:57 -0400 Subject: [PATCH 05/52] update stream sorting --- .../MediaBrowser.Model.Portable.csproj | 3 ++ .../MediaBrowser.Model.net35.csproj | 3 ++ MediaBrowser.Model/Dlna/StreamBuilder.cs | 24 +---------- MediaBrowser.Model/Dlna/StreamInfoSorter.cs | 40 +++++++++++++++++++ MediaBrowser.Model/MediaBrowser.Model.csproj | 1 + .../Drawing/ImageProcessor.cs | 6 +-- .../Photos/BaseDynamicImageProvider.cs | 16 +------- .../Udp/UdpServer.cs | 1 + Nuget/MediaBrowser.Common.Internal.nuspec | 4 +- Nuget/MediaBrowser.Common.nuspec | 2 +- Nuget/MediaBrowser.Model.Signed.nuspec | 2 +- Nuget/MediaBrowser.Server.Core.nuspec | 4 +- SharedVersion.cs | 4 +- 13 files changed, 61 insertions(+), 49 deletions(-) create mode 100644 MediaBrowser.Model/Dlna/StreamInfoSorter.cs diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj index 118e5659ad..ab77dc271b 100644 --- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj +++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj @@ -413,6 +413,9 @@ Dlna\StreamInfo.cs + + Dlna\StreamInfoSorter.cs + Dlna\SubtitleDeliveryMethod.cs diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj index a97bf2a8c6..b772e17539 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -378,6 +378,9 @@ Dlna\StreamInfo.cs + + Dlna\StreamInfoSorter.cs + Dlna\SubtitleDeliveryMethod.cs diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 7338f604c0..62ac321fe3 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -89,29 +89,7 @@ namespace MediaBrowser.Model.Dlna private StreamInfo GetOptimalStream(List streams) { - // Grab the first one that can be direct streamed - // If that doesn't produce anything, just take the first - foreach (StreamInfo i in streams) - { - if (i.PlayMethod == PlayMethod.DirectPlay && i.MediaSource.Protocol == MediaProtocol.File) - { - return i; - } - } - foreach (StreamInfo i in streams) - { - if (i.PlayMethod == PlayMethod.DirectPlay) - { - return i; - } - } - foreach (StreamInfo i in streams) - { - if (i.PlayMethod == PlayMethod.DirectStream) - { - return i; - } - } + streams = StreamInfoSorter.SortMediaSources(streams); foreach (StreamInfo stream in streams) { diff --git a/MediaBrowser.Model/Dlna/StreamInfoSorter.cs b/MediaBrowser.Model/Dlna/StreamInfoSorter.cs new file mode 100644 index 0000000000..80eca193f5 --- /dev/null +++ b/MediaBrowser.Model/Dlna/StreamInfoSorter.cs @@ -0,0 +1,40 @@ +using MediaBrowser.Model.MediaInfo; +using MediaBrowser.Model.Session; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MediaBrowser.Model.Dlna +{ + public class StreamInfoSorter + { + public static List SortMediaSources(List streams) + { + return streams.OrderBy(i => + { + switch (i.PlayMethod) + { + case PlayMethod.DirectPlay: + return 0; + case PlayMethod.DirectStream: + return 1; + case PlayMethod.Transcode: + return 2; + default: + throw new ArgumentException("Unrecognized PlayMethod"); + } + + }).ThenBy(i => + { + switch (i.MediaSource.Protocol) + { + case MediaProtocol.File: + return 0; + default: + return 1; + } + + }).ToList(); + } + } +} diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 30eec434db..fe67038df9 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -126,6 +126,7 @@ + diff --git a/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs b/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs index a9affe1ec0..5474892e81 100644 --- a/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs +++ b/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs @@ -268,8 +268,6 @@ namespace MediaBrowser.Server.Implementations.Drawing originalImage.CurrentImage.CompressionQuality = quality; originalImage.SaveImage(cacheFilePath); - - return cacheFilePath; } } else @@ -286,8 +284,6 @@ namespace MediaBrowser.Server.Implementations.Drawing wand.CurrentImage.CompressionQuality = quality; wand.SaveImage(cacheFilePath); - - return cacheFilePath; } } } @@ -296,6 +292,8 @@ namespace MediaBrowser.Server.Implementations.Drawing { semaphore.Release(); } + + return cacheFilePath; } private ImageFormat GetOutputFormat(ImageFormat requestedFormat) diff --git a/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs b/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs index 6fb02358e4..eac0cd96d9 100644 --- a/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs +++ b/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs @@ -223,8 +223,8 @@ namespace MediaBrowser.Server.Implementations.Photos protected virtual List GetFinalItems(List items, int limit) { - // Rotate the images no more than once per week - var random = new Random(GetWeekOfYear()).Next(); + // Rotate the images once every 7 days + var random = DateTime.Now.DayOfYear % 7; return items .OrderBy(i => (random + "" + items.IndexOf(i)).GetMD5()) @@ -233,18 +233,6 @@ namespace MediaBrowser.Server.Implementations.Photos .ToList(); } - private int GetWeekOfYear() - { - return DateTime.Now.Second; - var usCulture = new CultureInfo("en-US"); - var weekNo = usCulture.Calendar.GetWeekOfYear( - DateTime.Now, - usCulture.DateTimeFormat.CalendarWeekRule, - usCulture.DateTimeFormat.FirstDayOfWeek); - - return weekNo; - } - public int Order { get diff --git a/MediaBrowser.Server.Implementations/Udp/UdpServer.cs b/MediaBrowser.Server.Implementations/Udp/UdpServer.cs index 9dbffa9222..966129f4b9 100644 --- a/MediaBrowser.Server.Implementations/Udp/UdpServer.cs +++ b/MediaBrowser.Server.Implementations/Udp/UdpServer.cs @@ -51,6 +51,7 @@ namespace MediaBrowser.Server.Implementations.Udp AddMessageResponder("who is MediaBrowserServer?", RespondToV1Message); AddMessageResponder("who is MediaBrowserServer_v2?", RespondToV2Message); + AddMessageResponder("who is EmbyServer_v2?", RespondToV2Message); } private void AddMessageResponder(string message, Action responder) diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index 5f1c138b81..d229302bde 100644 --- a/Nuget/MediaBrowser.Common.Internal.nuspec +++ b/Nuget/MediaBrowser.Common.Internal.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common.Internal - 3.0.598 + 3.0.599 MediaBrowser.Common.Internal Luke ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption. Copyright © Media Browser 2013 - + diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index b5a0f3bba0..65205fa250 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common - 3.0.598 + 3.0.599 MediaBrowser.Common Media Browser Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Model.Signed.nuspec b/Nuget/MediaBrowser.Model.Signed.nuspec index 085408f2e2..2e829b436a 100644 --- a/Nuget/MediaBrowser.Model.Signed.nuspec +++ b/Nuget/MediaBrowser.Model.Signed.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Model.Signed - 3.0.598 + 3.0.599 MediaBrowser.Model - Signed Edition Media Browser Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index dca8aa61a0..f5d5b788bb 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Server.Core - 3.0.598 + 3.0.599 Media Browser.Server.Core Media Browser Team ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains core components required to build plugins for Media Browser Server. Copyright © Media Browser 2013 - + diff --git a/SharedVersion.cs b/SharedVersion.cs index 7528d4060b..fa4ca1ac7e 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,4 +1,4 @@ using System.Reflection; -//[assembly: AssemblyVersion("3.0.*")] -[assembly: AssemblyVersion("3.0.5557.20000")] +[assembly: AssemblyVersion("3.0.*")] +//[assembly: AssemblyVersion("3.0.5557.20000")] From 5e07bdf93c227bc98874ad333de4a6025fc53d6e Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 26 Mar 2015 19:10:34 -0400 Subject: [PATCH 06/52] sync fixes --- MediaBrowser.Api/Playback/MediaInfoService.cs | 16 ++++----- MediaBrowser.Model/Dlna/StreamInfoSorter.cs | 19 +++++++---- .../Library/LibraryManager.cs | 8 +++-- .../Photos/BaseDynamicImageProvider.cs | 6 ++-- .../Sync/MediaSync.cs | 33 ++++++++++++++++--- .../UserViews/StripCollageBuilder.cs | 2 +- 6 files changed, 59 insertions(+), 25 deletions(-) diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs index ce140365bb..e219f41869 100644 --- a/MediaBrowser.Api/Playback/MediaInfoService.cs +++ b/MediaBrowser.Api/Playback/MediaInfoService.cs @@ -86,14 +86,14 @@ namespace MediaBrowser.Api.Playback var authInfo = AuthorizationContext.GetAuthorizationInfo(Request); var profile = request.DeviceProfile; - //if (profile == null) - //{ - // var caps = _deviceManager.GetCapabilities(authInfo.DeviceId); - // if (caps != null) - // { - // profile = caps.DeviceProfile; - // } - //} + if (profile == null) + { + var caps = _deviceManager.GetCapabilities(authInfo.DeviceId); + if (caps != null) + { + profile = caps.DeviceProfile; + } + } if (profile != null) { diff --git a/MediaBrowser.Model/Dlna/StreamInfoSorter.cs b/MediaBrowser.Model/Dlna/StreamInfoSorter.cs index 80eca193f5..87c6615480 100644 --- a/MediaBrowser.Model/Dlna/StreamInfoSorter.cs +++ b/MediaBrowser.Model/Dlna/StreamInfoSorter.cs @@ -1,6 +1,5 @@ using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Session; -using System; using System.Collections.Generic; using System.Linq; @@ -11,17 +10,25 @@ namespace MediaBrowser.Model.Dlna public static List SortMediaSources(List streams) { return streams.OrderBy(i => + { + // Nothing beats direct playing a file + if (i.PlayMethod == PlayMethod.DirectPlay && i.MediaSource.Protocol == MediaProtocol.File) + { + return 0; + } + + return 1; + + }).ThenBy(i => { switch (i.PlayMethod) { + // Let's assume direct streaming a file is just as desirable as direct playing a remote url + case PlayMethod.DirectStream: case PlayMethod.DirectPlay: return 0; - case PlayMethod.DirectStream: - return 1; - case PlayMethod.Transcode: - return 2; default: - throw new ArgumentException("Unrecognized PlayMethod"); + return 2; } }).ThenBy(i => diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index 64e5d20c99..f9fa1aae35 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -1713,11 +1713,15 @@ namespace MediaBrowser.Server.Implementations.Library isNew = true; } - var refresh = isNew || (DateTime.UtcNow - item.DateLastSaved).TotalHours >= 6; + var refresh = isNew || (DateTime.UtcNow - item.DateLastSaved).TotalHours >= 12; if (refresh) { - _providerManagerFactory().QueueRefresh(item.Id, new MetadataRefreshOptions()); + _providerManagerFactory().QueueRefresh(item.Id, new MetadataRefreshOptions + { + // Need to force save to increment DateLastSaved + ForceSave = true + }); } return item; diff --git a/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs b/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs index eac0cd96d9..1063fde530 100644 --- a/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs +++ b/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs @@ -125,7 +125,7 @@ namespace MediaBrowser.Server.Implementations.Photos protected abstract Task> GetItemsWithImages(IHasImages item); - private const string Version = "4"; + private const string Version = "5"; protected string GetConfigurationCacheKey(List items, string itemName) { var parts = Version + "_" + (itemName ?? string.Empty) + "_" + @@ -223,8 +223,8 @@ namespace MediaBrowser.Server.Implementations.Photos protected virtual List GetFinalItems(List items, int limit) { - // Rotate the images once every 7 days - var random = DateTime.Now.DayOfYear % 7; + // Rotate the images once every x days + var random = DateTime.Now.DayOfYear % 4; return items .OrderBy(i => (random + "" + items.IndexOf(i)).GetMD5()) diff --git a/MediaBrowser.Server.Implementations/Sync/MediaSync.cs b/MediaBrowser.Server.Implementations/Sync/MediaSync.cs index 429b724898..815756f9b2 100644 --- a/MediaBrowser.Server.Implementations/Sync/MediaSync.cs +++ b/MediaBrowser.Server.Implementations/Sync/MediaSync.cs @@ -176,6 +176,8 @@ namespace MediaBrowser.Server.Implementations.Sync mediaSource.Path = sendFileResult.Path; mediaSource.Protocol = sendFileResult.Protocol; mediaSource.SupportsTranscoding = false; + + await SendSubtitles(localItem, mediaSource, provider, dataProvider, target, cancellationToken).ConfigureAwait(false); } } @@ -205,16 +207,37 @@ namespace MediaBrowser.Server.Implementations.Sync private async Task SendSubtitles(LocalItem localItem, MediaSourceInfo mediaSource, IServerSyncProvider provider, ISyncDataProvider dataProvider, SyncTarget target, CancellationToken cancellationToken) { + var failedSubtitles = new List(); + var requiresSave = false; + foreach (var mediaStream in mediaSource.MediaStreams .Where(i => i.Type == MediaStreamType.Subtitle && i.IsExternal) .ToList()) { - var sendFileResult = await SendFile(provider, mediaStream.Path, localItem, target, cancellationToken).ConfigureAwait(false); + try + { + var sendFileResult = await SendFile(provider, mediaStream.Path, localItem, target, cancellationToken).ConfigureAwait(false); - mediaStream.Path = sendFileResult.Path; - + mediaStream.Path = sendFileResult.Path; + requiresSave = true; + } + catch (Exception ex) + { + _logger.ErrorException("Error sending subtitle stream", ex); + failedSubtitles.Add(mediaStream); + } + } + + if (failedSubtitles.Count > 0) + { + mediaSource.MediaStreams = mediaSource.MediaStreams.Except(failedSubtitles).ToList(); + requiresSave = true; + } + + if (requiresSave) + { await dataProvider.AddOrUpdate(target, localItem).ConfigureAwait(false); - } + } } private async Task RemoveItem(IServerSyncProvider provider, @@ -374,7 +397,7 @@ namespace MediaBrowser.Server.Implementations.Sync var name = Path.GetFileNameWithoutExtension(item.LocalPath); - foreach (var file in list.Where(f => f.Name.Contains(name))) + foreach (var file in list.Where(f => f.Name.IndexOf(name, StringComparison.OrdinalIgnoreCase) != -1)) { var itemFile = new ItemFileInfo { diff --git a/MediaBrowser.Server.Implementations/UserViews/StripCollageBuilder.cs b/MediaBrowser.Server.Implementations/UserViews/StripCollageBuilder.cs index af2e014574..0bf4d8e4ac 100644 --- a/MediaBrowser.Server.Implementations/UserViews/StripCollageBuilder.cs +++ b/MediaBrowser.Server.Implementations/UserViews/StripCollageBuilder.cs @@ -162,7 +162,7 @@ namespace MediaBrowser.Server.Implementations.UserViews wandList.AddImage(mwr); int ex = (int)(wand.CurrentImage.Width - mwg.CurrentImage.Width) / 2; - wand.CurrentImage.CompositeImage(wandList.AppendImages(true), CompositeOperator.AtopCompositeOp, ex, Convert.ToInt32(height * .08)); + wand.CurrentImage.CompositeImage(wandList.AppendImages(true), CompositeOperator.AtopCompositeOp, ex, Convert.ToInt32(height * .085)); } } } From dfaf935f5ead9ecaf57a7b624d4a8de374b68735 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 26 Mar 2015 20:24:36 -0400 Subject: [PATCH 07/52] update udp --- MediaBrowser.Server.Implementations/Udp/UdpServer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Server.Implementations/Udp/UdpServer.cs b/MediaBrowser.Server.Implementations/Udp/UdpServer.cs index 966129f4b9..84b6d37088 100644 --- a/MediaBrowser.Server.Implementations/Udp/UdpServer.cs +++ b/MediaBrowser.Server.Implementations/Udp/UdpServer.cs @@ -51,7 +51,7 @@ namespace MediaBrowser.Server.Implementations.Udp AddMessageResponder("who is MediaBrowserServer?", RespondToV1Message); AddMessageResponder("who is MediaBrowserServer_v2?", RespondToV2Message); - AddMessageResponder("who is EmbyServer_v2?", RespondToV2Message); + AddMessageResponder("who is EmbyServer?", RespondToV2Message); } private void AddMessageResponder(string message, Action responder) From 7773862facd1e58137257726a96b680718b2aa06 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 27 Mar 2015 00:17:04 -0400 Subject: [PATCH 08/52] fix subtitle syncing --- .../ScheduledTasks/TaskManager.cs | 37 ++++++------ .../Images/LocalImageProvider.cs | 11 ++-- .../Drawing/ImageProcessor.cs | 58 ++++++++----------- .../Sync/MediaSync.cs | 51 ++++++++++++---- .../Sync/SyncJobProcessor.cs | 3 +- 5 files changed, 90 insertions(+), 70 deletions(-) diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/TaskManager.cs b/MediaBrowser.Common.Implementations/ScheduledTasks/TaskManager.cs index 4f84652c60..de7987bd22 100644 --- a/MediaBrowser.Common.Implementations/ScheduledTasks/TaskManager.cs +++ b/MediaBrowser.Common.Implementations/ScheduledTasks/TaskManager.cs @@ -6,6 +6,7 @@ using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Tasks; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -29,7 +30,8 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks /// /// The _task queue /// - private readonly SortedDictionary _taskQueue = new SortedDictionary(); + private readonly ConcurrentQueue> _taskQueue = + new ConcurrentQueue>(); /// /// Gets or sets the json serializer. @@ -136,25 +138,17 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks { var type = task.ScheduledTask.GetType(); + Logger.Info("Queueing task {0}", type.Name); + lock (_taskQueue) { - // If it's idle just execute immediately if (task.State == TaskState.Idle) { Execute(task, options); return; } - if (!_taskQueue.ContainsKey(type)) - { - Logger.Info("Queueing task {0}", type.Name); - _taskQueue.Add(type, options); - } - else - { - _taskQueue[type] = options; - Logger.Info("Task already queued: {0}", type.Name); - } + _taskQueue.Enqueue(new Tuple(type, options)); } } @@ -241,15 +235,24 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks // Execute queued tasks lock (_taskQueue) { - foreach (var enqueuedType in _taskQueue.ToList()) + var list = new List>(); + + Tuple item; + while (_taskQueue.TryDequeue(out item)) { - var scheduledTask = ScheduledTasks.First(t => t.ScheduledTask.GetType() == enqueuedType.Key); + if (list.All(i => i.Item1 != item.Item1)) + { + list.Add(item); + } + } + + foreach (var enqueuedType in list) + { + var scheduledTask = ScheduledTasks.First(t => t.ScheduledTask.GetType() == enqueuedType.Item1); if (scheduledTask.State == TaskState.Idle) { - Execute(scheduledTask, enqueuedType.Value); - - _taskQueue.Remove(enqueuedType.Key); + Execute(scheduledTask, enqueuedType.Item2); } } } diff --git a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs index 7f83aa61d9..6083c88dea 100644 --- a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs +++ b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs @@ -186,18 +186,17 @@ namespace MediaBrowser.LocalMetadata.Images names.Add("movie"); } - foreach (var name in names) - { - AddImage(files, images, imagePrefix + name, ImageType.Primary); - } - var fileNameWithoutExtension = item.FileNameWithoutExtension; - if (!string.IsNullOrEmpty(fileNameWithoutExtension)) { AddImage(files, images, fileNameWithoutExtension, ImageType.Primary); } + foreach (var name in names) + { + AddImage(files, images, imagePrefix + name, ImageType.Primary); + } + if (!isInMixedFolder) { foreach (var name in names) diff --git a/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs b/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs index 5474892e81..d78d5e8ea1 100644 --- a/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs +++ b/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs @@ -233,57 +233,45 @@ namespace MediaBrowser.Server.Implementations.Drawing await semaphore.WaitAsync().ConfigureAwait(false); - // Check again in case of lock contention - try - { - if (File.Exists(cacheFilePath)) - { - semaphore.Release(); - return cacheFilePath; - } - } - catch - { - semaphore.Release(); - throw; - } - try { CheckDisposed(); - var newWidth = Convert.ToInt32(newSize.Width); - var newHeight = Convert.ToInt32(newSize.Height); + if (!File.Exists(cacheFilePath)) + { + var newWidth = Convert.ToInt32(newSize.Width); + var newHeight = Convert.ToInt32(newSize.Height); - Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath)); + Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath)); - if (string.IsNullOrWhiteSpace(options.BackgroundColor)) - { - using (var originalImage = new MagickWand(originalImagePath)) + if (string.IsNullOrWhiteSpace(options.BackgroundColor)) { - originalImage.CurrentImage.ResizeImage(newWidth, newHeight); + using (var originalImage = new MagickWand(originalImagePath)) + { + originalImage.CurrentImage.ResizeImage(newWidth, newHeight); - DrawIndicator(originalImage, newWidth, newHeight, options); + DrawIndicator(originalImage, newWidth, newHeight, options); - originalImage.CurrentImage.CompressionQuality = quality; + originalImage.CurrentImage.CompressionQuality = quality; - originalImage.SaveImage(cacheFilePath); + originalImage.SaveImage(cacheFilePath); + } } - } - else - { - using (var wand = new MagickWand(newWidth, newHeight, options.BackgroundColor)) + else { - using (var originalImage = new MagickWand(originalImagePath)) + using (var wand = new MagickWand(newWidth, newHeight, options.BackgroundColor)) { - originalImage.CurrentImage.ResizeImage(newWidth, newHeight); + using (var originalImage = new MagickWand(originalImagePath)) + { + originalImage.CurrentImage.ResizeImage(newWidth, newHeight); - wand.CurrentImage.CompositeImage(originalImage, CompositeOperator.OverCompositeOp, 0, 0); - DrawIndicator(wand, newWidth, newHeight, options); + wand.CurrentImage.CompositeImage(originalImage, CompositeOperator.OverCompositeOp, 0, 0); + DrawIndicator(wand, newWidth, newHeight, options); - wand.CurrentImage.CompressionQuality = quality; + wand.CurrentImage.CompressionQuality = quality; - wand.SaveImage(cacheFilePath); + wand.SaveImage(cacheFilePath); + } } } } diff --git a/MediaBrowser.Server.Implementations/Sync/MediaSync.cs b/MediaBrowser.Server.Implementations/Sync/MediaSync.cs index 815756f9b2..c5b4abf62e 100644 --- a/MediaBrowser.Server.Implementations/Sync/MediaSync.cs +++ b/MediaBrowser.Server.Implementations/Sync/MediaSync.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Common.IO; +using System.IO; +using MediaBrowser.Common.IO; using MediaBrowser.Common.Progress; using MediaBrowser.Controller; using MediaBrowser.Controller.Sync; @@ -9,7 +10,6 @@ using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Sync; using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Security.Cryptography; using System.Text; @@ -152,7 +152,7 @@ namespace MediaBrowser.Server.Implementations.Sync try { - var sendFileResult = await SendFile(provider, internalSyncJobItem.OutputPath, localItem, target, cancellationToken).ConfigureAwait(false); + var sendFileResult = await SendFile(provider, internalSyncJobItem.OutputPath, localItem.LocalPath, target, cancellationToken).ConfigureAwait(false); if (localItem.Item.MediaSources != null) { @@ -173,10 +173,6 @@ namespace MediaBrowser.Server.Implementations.Sync var mediaSource = localItem.Item.MediaSources.FirstOrDefault(); if (mediaSource != null) { - mediaSource.Path = sendFileResult.Path; - mediaSource.Protocol = sendFileResult.Protocol; - mediaSource.SupportsTranscoding = false; - await SendSubtitles(localItem, mediaSource, provider, dataProvider, target, cancellationToken).ConfigureAwait(false); } } @@ -216,7 +212,8 @@ namespace MediaBrowser.Server.Implementations.Sync { try { - var sendFileResult = await SendFile(provider, mediaStream.Path, localItem, target, cancellationToken).ConfigureAwait(false); + var remotePath = GetRemoteSubtitlePath(localItem, mediaStream, provider, target); + var sendFileResult = await SendFile(provider, mediaStream.Path, remotePath, target, cancellationToken).ConfigureAwait(false); mediaStream.Path = sendFileResult.Path; requiresSave = true; @@ -240,6 +237,38 @@ namespace MediaBrowser.Server.Implementations.Sync } } + private string GetRemoteSubtitlePath(LocalItem item, MediaStream stream, IServerSyncProvider provider, SyncTarget target) + { + var path = item.LocalPath; + + var filename = GetSubtitleSaveFileName(item, stream.Language, stream.IsForced) + "." + stream.Codec.ToLower(); + + var parentPath = provider.GetParentDirectoryPath(path, target); + + path = Path.Combine(parentPath, filename); + + return path; + } + + private string GetSubtitleSaveFileName(LocalItem item, string language, bool isForced) + { + var path = item.LocalPath; + + var name = Path.GetFileNameWithoutExtension(path); + + if (!string.IsNullOrWhiteSpace(language)) + { + name += "." + language.ToLower(); + } + + if (isForced) + { + name += ".foreign"; + } + + return name; + } + private async Task RemoveItem(IServerSyncProvider provider, ISyncDataProvider dataProvider, string serverId, @@ -264,12 +293,12 @@ namespace MediaBrowser.Server.Implementations.Sync } } - private async Task SendFile(IServerSyncProvider provider, string inputPath, LocalItem item, SyncTarget target, CancellationToken cancellationToken) + private async Task SendFile(IServerSyncProvider provider, string inputPath, string remotePath, SyncTarget target, CancellationToken cancellationToken) { - _logger.Debug("Sending {0} to {1}. Remote path: {2}", inputPath, provider.Name, item.LocalPath); + _logger.Debug("Sending {0} to {1}. Remote path: {2}", inputPath, provider.Name, remotePath); using (var stream = _fileSystem.GetFileStream(inputPath, FileMode.Open, FileAccess.Read, FileShare.Read, true)) { - return await provider.SendFile(stream, item.LocalPath, target, new Progress(), cancellationToken).ConfigureAwait(false); + return await provider.SendFile(stream, remotePath, target, new Progress(), cancellationToken).ConfigureAwait(false); } } diff --git a/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs b/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs index 22c59610b9..b73e0e85f2 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs @@ -632,7 +632,8 @@ namespace MediaBrowser.Server.Implementations.Sync IsExternal = true, Language = subtitle.Language, Path = fileInfo.Path, - SupportsExternalStream = true + SupportsExternalStream = true, + Type = MediaStreamType.Subtitle }); startingIndex++; From d7a979979befd30389898138c742b57d1062e8db Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 27 Mar 2015 00:42:41 -0400 Subject: [PATCH 09/52] sync updates --- MediaBrowser.Server.Implementations/Sync/MediaSync.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/MediaBrowser.Server.Implementations/Sync/MediaSync.cs b/MediaBrowser.Server.Implementations/Sync/MediaSync.cs index c5b4abf62e..936f1b1fe8 100644 --- a/MediaBrowser.Server.Implementations/Sync/MediaSync.cs +++ b/MediaBrowser.Server.Implementations/Sync/MediaSync.cs @@ -152,7 +152,7 @@ namespace MediaBrowser.Server.Implementations.Sync try { - var sendFileResult = await SendFile(provider, internalSyncJobItem.OutputPath, localItem.LocalPath, target, cancellationToken).ConfigureAwait(false); + var sendFileResult = await SendFile(provider, internalSyncJobItem.OutputPath, localItem.LocalPath, target, fileTransferProgress, cancellationToken).ConfigureAwait(false); if (localItem.Item.MediaSources != null) { @@ -213,7 +213,7 @@ namespace MediaBrowser.Server.Implementations.Sync try { var remotePath = GetRemoteSubtitlePath(localItem, mediaStream, provider, target); - var sendFileResult = await SendFile(provider, mediaStream.Path, remotePath, target, cancellationToken).ConfigureAwait(false); + var sendFileResult = await SendFile(provider, mediaStream.Path, remotePath, target, new Progress(), cancellationToken).ConfigureAwait(false); mediaStream.Path = sendFileResult.Path; requiresSave = true; @@ -293,12 +293,12 @@ namespace MediaBrowser.Server.Implementations.Sync } } - private async Task SendFile(IServerSyncProvider provider, string inputPath, string remotePath, SyncTarget target, CancellationToken cancellationToken) + private async Task SendFile(IServerSyncProvider provider, string inputPath, string remotePath, SyncTarget target, IProgress progress, CancellationToken cancellationToken) { _logger.Debug("Sending {0} to {1}. Remote path: {2}", inputPath, provider.Name, remotePath); using (var stream = _fileSystem.GetFileStream(inputPath, FileMode.Open, FileAccess.Read, FileShare.Read, true)) { - return await provider.SendFile(stream, remotePath, target, new Progress(), cancellationToken).ConfigureAwait(false); + return await provider.SendFile(stream, remotePath, target, progress, cancellationToken).ConfigureAwait(false); } } From d8cbd649176566dbdcc49e72d0fa2ddd4f25d536 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 27 Mar 2015 16:55:31 -0400 Subject: [PATCH 10/52] fix mp4 sync encoding --- .../Channels/ChannelMediaInfo.cs | 5 +- .../Encoder/VideoEncoder.cs | 9 ++-- .../ApiClient/ServerCredentials.cs | 21 +++++++-- MediaBrowser.Model/ApiClient/ServerInfo.cs | 8 +++- MediaBrowser.Model/Dlna/StreamInfoSorter.cs | 2 +- .../LiveTv/LiveTvManager.cs | 46 +++++++++++++------ .../Sync/MediaSync.cs | 15 +++--- .../Native/BaseMonoApp.cs | 1 - Nuget/MediaBrowser.Common.Internal.nuspec | 4 +- Nuget/MediaBrowser.Common.nuspec | 2 +- Nuget/MediaBrowser.Model.Signed.nuspec | 2 +- Nuget/MediaBrowser.Server.Core.nuspec | 4 +- SharedVersion.cs | 4 +- 13 files changed, 82 insertions(+), 41 deletions(-) diff --git a/MediaBrowser.Controller/Channels/ChannelMediaInfo.cs b/MediaBrowser.Controller/Channels/ChannelMediaInfo.cs index 94fa1ac02f..1672b75fac 100644 --- a/MediaBrowser.Controller/Channels/ChannelMediaInfo.cs +++ b/MediaBrowser.Controller/Channels/ChannelMediaInfo.cs @@ -38,6 +38,7 @@ namespace MediaBrowser.Controller.Channels public string Id { get; set; } public bool ReadAtNativeFramerate { get; set; } + public bool SupportsDirectPlay { get; set; } public ChannelMediaInfo() { @@ -45,6 +46,7 @@ namespace MediaBrowser.Controller.Channels // This is most common Protocol = MediaProtocol.Http; + SupportsDirectPlay = true; } public MediaSourceInfo ToMediaSource() @@ -63,7 +65,8 @@ namespace MediaBrowser.Controller.Channels Name = id, Id = id, ReadAtNativeFramerate = ReadAtNativeFramerate, - SupportsDirectStream = Protocol == MediaProtocol.File || Protocol == MediaProtocol.Http + SupportsDirectStream = Protocol == MediaProtocol.File || Protocol == MediaProtocol.Http, + SupportsDirectPlay = SupportsDirectPlay }; var bitrate = (AudioBitrate ?? 0) + (VideoBitrate ?? 0); diff --git a/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs index a714b160ec..b8ed97b1fc 100644 --- a/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs @@ -73,10 +73,13 @@ namespace MediaBrowser.MediaEncoding.Encoder args; } - var keyFrameArg = string.Format(" -force_key_frames expr:gte(t,n_forced*{0})", - 5.ToString(UsCulture)); + if (state.Options.Context == EncodingContext.Streaming) + { + var keyFrameArg = string.Format(" -force_key_frames expr:gte(t,n_forced*{0})", + 5.ToString(UsCulture)); - args += keyFrameArg; + args += keyFrameArg; + } var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream; diff --git a/MediaBrowser.Model/ApiClient/ServerCredentials.cs b/MediaBrowser.Model/ApiClient/ServerCredentials.cs index d911f81211..6ba2a80c87 100644 --- a/MediaBrowser.Model/ApiClient/ServerCredentials.cs +++ b/MediaBrowser.Model/ApiClient/ServerCredentials.cs @@ -1,7 +1,6 @@ using MediaBrowser.Model.Extensions; using System; using System.Collections.Generic; -using System.Linq; namespace MediaBrowser.Model.ApiClient { @@ -24,7 +23,12 @@ namespace MediaBrowser.Model.ApiClient throw new ArgumentNullException("server"); } - var list = Servers.ToList(); + // Clone the existing list of servers + var list = new List(); + foreach (ServerInfo serverInfo in Servers) + { + list.Add(serverInfo); + } var index = FindIndex(list, server.Id); @@ -32,8 +36,11 @@ namespace MediaBrowser.Model.ApiClient { var existing = list[index]; - // Merge the data - existing.DateLastAccessed = new[] { existing.DateLastAccessed, server.DateLastAccessed }.Max(); + // Take the most recent DateLastAccessed + if (server.DateLastAccessed > existing.DateLastAccessed) + { + existing.DateLastAccessed = server.DateLastAccessed; + } existing.UserLinkType = server.UserLinkType; @@ -64,7 +71,11 @@ namespace MediaBrowser.Model.ApiClient } if (server.WakeOnLanInfos != null && server.WakeOnLanInfos.Count > 0) { - existing.WakeOnLanInfos = server.WakeOnLanInfos.ToList(); + existing.WakeOnLanInfos = new List(); + foreach (WakeOnLanInfo info in server.WakeOnLanInfos) + { + existing.WakeOnLanInfos.Add(info); + } } if (server.LastConnectionMode.HasValue) { diff --git a/MediaBrowser.Model/ApiClient/ServerInfo.cs b/MediaBrowser.Model/ApiClient/ServerInfo.cs index cc062f2f60..e1fa581d7b 100644 --- a/MediaBrowser.Model/ApiClient/ServerInfo.cs +++ b/MediaBrowser.Model/ApiClient/ServerInfo.cs @@ -3,7 +3,6 @@ using MediaBrowser.Model.Extensions; using MediaBrowser.Model.System; using System; using System.Collections.Generic; -using System.Linq; namespace MediaBrowser.Model.ApiClient { @@ -83,7 +82,12 @@ namespace MediaBrowser.Model.ApiClient throw new ArgumentNullException("user"); } - var list = Users.ToList(); + // Clone the existing list of users + var list = new List(); + foreach (ServerUserInfo serverUserInfo in Users) + { + list.Add(serverUserInfo); + } var index = FindIndex(list, user.Id); diff --git a/MediaBrowser.Model/Dlna/StreamInfoSorter.cs b/MediaBrowser.Model/Dlna/StreamInfoSorter.cs index 87c6615480..0cccd80804 100644 --- a/MediaBrowser.Model/Dlna/StreamInfoSorter.cs +++ b/MediaBrowser.Model/Dlna/StreamInfoSorter.cs @@ -28,7 +28,7 @@ namespace MediaBrowser.Model.Dlna case PlayMethod.DirectPlay: return 0; default: - return 2; + return 1; } }).ThenBy(i => diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index 2bc6cbadfe..59daa4921a 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -333,10 +333,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv try { MediaSourceInfo info; + var isVideo = true; if (isChannel) { var channel = GetInternalChannel(id); + isVideo = channel.ChannelType == ChannelType.TV; var service = GetService(channel); _logger.Info("Opening channel stream from {0}, external channel Id: {1}", service.Name, channel.ExternalId); info = await service.GetChannelStream(channel.ExternalId, null, cancellationToken).ConfigureAwait(false); @@ -344,6 +346,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv else { var recording = await GetInternalRecording(id, cancellationToken).ConfigureAwait(false); + isVideo = !string.Equals(recording.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase); var service = GetService(recording); _logger.Info("Opening recording stream from {0}, external recording Id: {1}", service.Name, recording.RecordingInfo.Id); @@ -351,7 +354,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv } _logger.Info("Live stream info: {0}", _jsonSerializer.SerializeToString(info)); - Sanitize(info); + Normalize(info, isVideo); var data = new LiveStreamData { @@ -377,25 +380,40 @@ namespace MediaBrowser.Server.Implementations.LiveTv } } - private void Sanitize(MediaSourceInfo mediaSource) + private void Normalize(MediaSourceInfo mediaSource, bool isVideo) { if (mediaSource.MediaStreams.Count == 0) { - mediaSource.MediaStreams.AddRange(new List + if (isVideo) { - new MediaStream + mediaSource.MediaStreams.AddRange(new List { - Type = MediaStreamType.Video, - // Set the index to -1 because we don't know the exact index of the video stream within the container - Index = -1 - }, - new MediaStream + new MediaStream + { + Type = MediaStreamType.Video, + // Set the index to -1 because we don't know the exact index of the video stream within the container + Index = -1 + }, + new MediaStream + { + Type = MediaStreamType.Audio, + // Set the index to -1 because we don't know the exact index of the audio stream within the container + Index = -1 + } + }); + } + else + { + mediaSource.MediaStreams.AddRange(new List { - Type = MediaStreamType.Audio, - // Set the index to -1 because we don't know the exact index of the audio stream within the container - Index = -1 - } - }); + new MediaStream + { + Type = MediaStreamType.Audio, + // Set the index to -1 because we don't know the exact index of the audio stream within the container + Index = -1 + } + }); + } } // Clean some bad data coming from providers diff --git a/MediaBrowser.Server.Implementations/Sync/MediaSync.cs b/MediaBrowser.Server.Implementations/Sync/MediaSync.cs index 936f1b1fe8..cac0e15546 100644 --- a/MediaBrowser.Server.Implementations/Sync/MediaSync.cs +++ b/MediaBrowser.Server.Implementations/Sync/MediaSync.cs @@ -1,5 +1,4 @@ -using System.IO; -using MediaBrowser.Common.IO; +using MediaBrowser.Common.IO; using MediaBrowser.Common.Progress; using MediaBrowser.Controller; using MediaBrowser.Controller.Sync; @@ -10,6 +9,7 @@ using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Sync; using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Security.Cryptography; using System.Text; @@ -140,9 +140,6 @@ namespace MediaBrowser.Server.Implementations.Sync var libraryItem = jobItem.Item; var internalSyncJobItem = _syncManager.GetJobItem(jobItem.SyncJobItemId); - var fileTransferProgress = new ActionableProgress(); - fileTransferProgress.RegisterAction(pct => progress.Report(pct * .92)); - var localItem = CreateLocalItem(provider, jobItem, target, libraryItem, serverId, jobItem.OriginalFileName); await _syncManager.ReportSyncJobItemTransferBeginning(internalSyncJobItem.Id); @@ -152,6 +149,9 @@ namespace MediaBrowser.Server.Implementations.Sync try { + var fileTransferProgress = new ActionableProgress(); + fileTransferProgress.RegisterAction(pct => progress.Report(pct * .92)); + var sendFileResult = await SendFile(provider, internalSyncJobItem.OutputPath, localItem.LocalPath, target, fileTransferProgress, cancellationToken).ConfigureAwait(false); if (localItem.Item.MediaSources != null) @@ -342,7 +342,10 @@ namespace MediaBrowser.Server.Implementations.Sync private string GetSyncJobFolderName(SyncedItem syncedItem, IServerSyncProvider provider) { - var name = syncedItem.SyncJobName + syncedItem.SyncJobDateCreated.ToLocalTime().ToString("g"); + var name = syncedItem.SyncJobName + "-" + syncedItem.SyncJobDateCreated + .ToLocalTime() + .ToString("g") + .Replace(" ", "-"); name = GetValidFilename(provider, name); diff --git a/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs b/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs index 1ec0109ad8..aea6d73679 100644 --- a/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs +++ b/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs @@ -139,7 +139,6 @@ namespace MediaBrowser.Server.Mono.Native } else if (string.Equals(sysName, "BSD", StringComparison.OrdinalIgnoreCase)) { - // TODO: How to detect BSD? info.OperatingSystem = Startup.Common.OperatingSystem.Bsd; } diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index d229302bde..dccef68ed4 100644 --- a/Nuget/MediaBrowser.Common.Internal.nuspec +++ b/Nuget/MediaBrowser.Common.Internal.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common.Internal - 3.0.599 + 3.0.600 MediaBrowser.Common.Internal Luke ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption. Copyright © Media Browser 2013 - + diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index 65205fa250..b35ce68d6d 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common - 3.0.599 + 3.0.600 MediaBrowser.Common Media Browser Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Model.Signed.nuspec b/Nuget/MediaBrowser.Model.Signed.nuspec index 2e829b436a..ea45fdfe19 100644 --- a/Nuget/MediaBrowser.Model.Signed.nuspec +++ b/Nuget/MediaBrowser.Model.Signed.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Model.Signed - 3.0.599 + 3.0.600 MediaBrowser.Model - Signed Edition Media Browser Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index f5d5b788bb..6fc7e46393 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Server.Core - 3.0.599 + 3.0.600 Media Browser.Server.Core Media Browser Team ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains core components required to build plugins for Media Browser Server. Copyright © Media Browser 2013 - + diff --git a/SharedVersion.cs b/SharedVersion.cs index fa4ca1ac7e..621b97a63d 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,4 +1,4 @@ using System.Reflection; -[assembly: AssemblyVersion("3.0.*")] -//[assembly: AssemblyVersion("3.0.5557.20000")] +//[assembly: AssemblyVersion("3.0.*")] +[assembly: AssemblyVersion("3.0.5557.30000")] From d12bcc2d249bce7f04c7927058366dff49161098 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 27 Mar 2015 22:19:20 -0400 Subject: [PATCH 11/52] sync updates --- .../MediaBrowser.Controller.csproj | 1 + .../Sync/IRequiresDynamicAccess.cs | 18 +++++++ .../Sync/IServerSyncProvider.cs | 9 ---- MediaBrowser.Model/Entities/MediaStream.cs | 6 +++ MediaBrowser.Model/Sync/LocalItem.cs | 6 +++ .../Sync/MediaSync.cs | 52 ++++--------------- .../Sync/SyncedMediaSourceProvider.cs | 51 ++++++++++++++++-- Nuget/MediaBrowser.Common.Internal.nuspec | 4 +- Nuget/MediaBrowser.Common.nuspec | 2 +- Nuget/MediaBrowser.Model.Signed.nuspec | 2 +- Nuget/MediaBrowser.Server.Core.nuspec | 4 +- 11 files changed, 95 insertions(+), 60 deletions(-) create mode 100644 MediaBrowser.Controller/Sync/IRequiresDynamicAccess.cs diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index d7a69ceb22..809d1f6f46 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -393,6 +393,7 @@ + diff --git a/MediaBrowser.Controller/Sync/IRequiresDynamicAccess.cs b/MediaBrowser.Controller/Sync/IRequiresDynamicAccess.cs new file mode 100644 index 0000000000..820a1dce08 --- /dev/null +++ b/MediaBrowser.Controller/Sync/IRequiresDynamicAccess.cs @@ -0,0 +1,18 @@ +using MediaBrowser.Model.Sync; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.Sync +{ + public interface IRequiresDynamicAccess + { + /// + /// Gets the file information. + /// + /// The remote path. + /// The target. + /// The cancellation token. + /// Task<SendFileResult>. + Task GetFileInfo(string remotePath, SyncTarget target, CancellationToken cancellationToken); + } +} diff --git a/MediaBrowser.Controller/Sync/IServerSyncProvider.cs b/MediaBrowser.Controller/Sync/IServerSyncProvider.cs index abf884e9d5..9cccd41509 100644 --- a/MediaBrowser.Controller/Sync/IServerSyncProvider.cs +++ b/MediaBrowser.Controller/Sync/IServerSyncProvider.cs @@ -54,14 +54,5 @@ namespace MediaBrowser.Controller.Sync /// The target. /// System.String. string GetParentDirectoryPath(string path, SyncTarget target); - - /// - /// Gets the file system entries. - /// - /// The path. - /// The target. - /// The cancellation token. - /// Task<List<DeviceFileInfo>>. - Task> GetFileSystemEntries(string path, SyncTarget target, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index 4af32bb500..66fb486284 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -168,6 +168,12 @@ namespace MediaBrowser.Model.Entities /// The filename. public string Path { get; set; } + /// + /// Gets or sets the external identifier. + /// + /// The external identifier. + public string ExternalId { get; set; } + /// /// Gets or sets the pixel format. /// diff --git a/MediaBrowser.Model/Sync/LocalItem.cs b/MediaBrowser.Model/Sync/LocalItem.cs index c6a10298fe..37f605a590 100644 --- a/MediaBrowser.Model/Sync/LocalItem.cs +++ b/MediaBrowser.Model/Sync/LocalItem.cs @@ -35,9 +35,15 @@ namespace MediaBrowser.Model.Sync /// /// The user ids with access. public List UserIdsWithAccess { get; set; } + /// + /// Gets or sets the additional files. + /// + /// The additional files. + public List AdditionalFiles { get; set; } public LocalItem() { + AdditionalFiles = new List(); UserIdsWithAccess = new List(); } } diff --git a/MediaBrowser.Server.Implementations/Sync/MediaSync.cs b/MediaBrowser.Server.Implementations/Sync/MediaSync.cs index cac0e15546..befddabd8a 100644 --- a/MediaBrowser.Server.Implementations/Sync/MediaSync.cs +++ b/MediaBrowser.Server.Implementations/Sync/MediaSync.cs @@ -215,6 +215,13 @@ namespace MediaBrowser.Server.Implementations.Sync var remotePath = GetRemoteSubtitlePath(localItem, mediaStream, provider, target); var sendFileResult = await SendFile(provider, mediaStream.Path, remotePath, target, new Progress(), cancellationToken).ConfigureAwait(false); + // This is the path that will be used when talking to the provider + mediaStream.ExternalId = remotePath; + + // Keep track of all additional files for cleanup later. + localItem.AdditionalFiles.Add(remotePath); + + // This is the public path clients will use mediaStream.Path = sendFileResult.Path; requiresSave = true; } @@ -280,13 +287,14 @@ namespace MediaBrowser.Server.Implementations.Sync foreach (var localItem in localItems) { - var files = await GetFiles(provider, localItem, target, cancellationToken); + var files = localItem.AdditionalFiles.ToList(); + files.Insert(0, localItem.LocalPath); foreach (var file in files) { - _logger.Debug("Removing {0} from {1}.", file.Path, target.Name); + _logger.Debug("Removing {0} from {1}.", file, target.Name); - await provider.DeleteFile(file.Path, target, cancellationToken).ConfigureAwait(false); + await provider.DeleteFile(file, target, cancellationToken).ConfigureAwait(false); } await dataProvider.Delete(target, localItem.Id).ConfigureAwait(false); @@ -417,43 +425,5 @@ namespace MediaBrowser.Server.Implementations.Sync // We can always add this method to the sync provider if it's really needed return _fileSystem.GetValidFilename(filename); } - - private async Task> GetFiles(IServerSyncProvider provider, LocalItem item, SyncTarget target, CancellationToken cancellationToken) - { - var path = item.LocalPath; - path = provider.GetParentDirectoryPath(path, target); - - var list = await provider.GetFileSystemEntries(path, target, cancellationToken).ConfigureAwait(false); - - var itemFiles = new List(); - - var name = Path.GetFileNameWithoutExtension(item.LocalPath); - - foreach (var file in list.Where(f => f.Name.IndexOf(name, StringComparison.OrdinalIgnoreCase) != -1)) - { - var itemFile = new ItemFileInfo - { - Path = file.Path, - Name = file.Name - }; - - if (IsSubtitleFile(file.Name)) - { - itemFile.Type = ItemFileType.Subtitles; - } - - itemFiles.Add(itemFile); - } - - return itemFiles; - } - - private static readonly string[] SupportedSubtitleExtensions = { ".srt", ".vtt" }; - private bool IsSubtitleFile(string path) - { - var ext = Path.GetExtension(path) ?? string.Empty; - - return SupportedSubtitleExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase); - } } } diff --git a/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs b/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs index 893b16b140..e7e1d1c633 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs @@ -3,6 +3,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Sync; using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Logging; using MediaBrowser.Model.Sync; using System; using System.Collections.Generic; @@ -16,10 +17,12 @@ namespace MediaBrowser.Server.Implementations.Sync { private readonly SyncManager _syncManager; private readonly IServerApplicationHost _appHost; + private readonly ILogger _logger; - public SyncedMediaSourceProvider(ISyncManager syncManager, IServerApplicationHost appHost) + public SyncedMediaSourceProvider(ISyncManager syncManager, IServerApplicationHost appHost, ILogger logger) { _appHost = appHost; + _logger = logger; _syncManager = (SyncManager)syncManager; } @@ -28,7 +31,7 @@ namespace MediaBrowser.Server.Implementations.Sync var jobItemResult = _syncManager.GetJobItems(new SyncJobItemQuery { AddMetadata = false, - Statuses = new SyncJobItemStatus[] { SyncJobItemStatus.Synced }, + Statuses = new[] { SyncJobItemStatus.Synced }, ItemId = item.Id.ToString("N") }); @@ -49,14 +52,17 @@ namespace MediaBrowser.Server.Implementations.Sync if (targetTuple != null) { var syncTarget = targetTuple.Item2; - + var syncProvider = targetTuple.Item1; var dataProvider = _syncManager.GetDataProvider(targetTuple.Item1, syncTarget); var localItems = await dataProvider.GetCachedItems(syncTarget, serverId, item.Id.ToString("N")).ConfigureAwait(false); foreach (var localItem in localItems) { - list.AddRange(localItem.Item.MediaSources); + foreach (var mediaSource in localItem.Item.MediaSources) + { + await TryAddMediaSource(list, localItem, mediaSource, syncProvider, syncTarget, cancellationToken).ConfigureAwait(false); + } } } } @@ -64,5 +70,42 @@ namespace MediaBrowser.Server.Implementations.Sync return list; } + + private async Task TryAddMediaSource(List list, + LocalItem item, + MediaSourceInfo mediaSource, + IServerSyncProvider provider, + SyncTarget target, + CancellationToken cancellationToken) + { + var requiresDynamicAccess = provider as IRequiresDynamicAccess; + + if (requiresDynamicAccess == null) + { + list.Add(mediaSource); + return; + } + + try + { + var dynamicInfo = await requiresDynamicAccess.GetFileInfo(item.LocalPath, target, cancellationToken).ConfigureAwait(false); + + foreach (var stream in mediaSource.MediaStreams) + { + var dynamicStreamInfo = await requiresDynamicAccess.GetFileInfo(stream.ExternalId, target, cancellationToken).ConfigureAwait(false); + + stream.Path = dynamicStreamInfo.Path; + } + + mediaSource.Path = dynamicInfo.Path; + mediaSource.Protocol = dynamicInfo.Protocol; + + list.Add(mediaSource); + } + catch (Exception ex) + { + _logger.ErrorException("Error getting dynamic media source info", ex); + } + } } } diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index dccef68ed4..84187e72bc 100644 --- a/Nuget/MediaBrowser.Common.Internal.nuspec +++ b/Nuget/MediaBrowser.Common.Internal.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common.Internal - 3.0.600 + 3.0.602 MediaBrowser.Common.Internal Luke ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption. Copyright © Media Browser 2013 - + diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index b35ce68d6d..59ec2aa65b 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common - 3.0.600 + 3.0.602 MediaBrowser.Common Media Browser Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Model.Signed.nuspec b/Nuget/MediaBrowser.Model.Signed.nuspec index ea45fdfe19..6f4b6a7e39 100644 --- a/Nuget/MediaBrowser.Model.Signed.nuspec +++ b/Nuget/MediaBrowser.Model.Signed.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Model.Signed - 3.0.600 + 3.0.602 MediaBrowser.Model - Signed Edition Media Browser Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index 6fc7e46393..4d919a965e 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Server.Core - 3.0.600 + 3.0.602 Media Browser.Server.Core Media Browser Team ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains core components required to build plugins for Media Browser Server. Copyright © Media Browser 2013 - + From bda0b2f7c490fbd2f462902aba13ee7db80688c3 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 28 Mar 2015 01:07:29 -0400 Subject: [PATCH 12/52] sync updates --- .../MediaBrowser.Controller.csproj | 4 ++-- ...equiresDynamicAccess.cs => IHasDynamicAccess.cs} | 8 ++++---- MediaBrowser.Controller/Sync/IServerSyncProvider.cs | 2 +- .../Sync/{SendFileResult.cs => SyncedFileInfo.cs} | 13 ++++++++++++- .../Sync/MediaSync.cs | 2 +- .../Sync/SyncedMediaSourceProvider.cs | 6 +++--- Nuget/MediaBrowser.Common.Internal.nuspec | 4 ++-- Nuget/MediaBrowser.Common.nuspec | 2 +- Nuget/MediaBrowser.Model.Signed.nuspec | 2 +- Nuget/MediaBrowser.Server.Core.nuspec | 4 ++-- 10 files changed, 29 insertions(+), 18 deletions(-) rename MediaBrowser.Controller/Sync/{IRequiresDynamicAccess.cs => IHasDynamicAccess.cs} (58%) rename MediaBrowser.Controller/Sync/{SendFileResult.cs => SyncedFileInfo.cs} (50%) diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 809d1f6f46..2fa62f79f2 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -393,13 +393,13 @@ - + - + diff --git a/MediaBrowser.Controller/Sync/IRequiresDynamicAccess.cs b/MediaBrowser.Controller/Sync/IHasDynamicAccess.cs similarity index 58% rename from MediaBrowser.Controller/Sync/IRequiresDynamicAccess.cs rename to MediaBrowser.Controller/Sync/IHasDynamicAccess.cs index 820a1dce08..f907de7290 100644 --- a/MediaBrowser.Controller/Sync/IRequiresDynamicAccess.cs +++ b/MediaBrowser.Controller/Sync/IHasDynamicAccess.cs @@ -4,15 +4,15 @@ using System.Threading.Tasks; namespace MediaBrowser.Controller.Sync { - public interface IRequiresDynamicAccess + public interface IHasDynamicAccess { /// - /// Gets the file information. + /// Gets the synced file information. /// /// The remote path. /// The target. /// The cancellation token. - /// Task<SendFileResult>. - Task GetFileInfo(string remotePath, SyncTarget target, CancellationToken cancellationToken); + /// Task<SyncedFileInfo>. + Task GetSyncedFileInfo(string remotePath, SyncTarget target, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Controller/Sync/IServerSyncProvider.cs b/MediaBrowser.Controller/Sync/IServerSyncProvider.cs index 9cccd41509..46bbbd3299 100644 --- a/MediaBrowser.Controller/Sync/IServerSyncProvider.cs +++ b/MediaBrowser.Controller/Sync/IServerSyncProvider.cs @@ -18,7 +18,7 @@ namespace MediaBrowser.Controller.Sync /// The progress. /// The cancellation token. /// Task. - Task SendFile(Stream stream, string remotePath, SyncTarget target, IProgress progress, CancellationToken cancellationToken); + Task SendFile(Stream stream, string remotePath, SyncTarget target, IProgress progress, CancellationToken cancellationToken); /// /// Deletes the file. diff --git a/MediaBrowser.Controller/Sync/SendFileResult.cs b/MediaBrowser.Controller/Sync/SyncedFileInfo.cs similarity index 50% rename from MediaBrowser.Controller/Sync/SendFileResult.cs rename to MediaBrowser.Controller/Sync/SyncedFileInfo.cs index 62753444a4..550af2d554 100644 --- a/MediaBrowser.Controller/Sync/SendFileResult.cs +++ b/MediaBrowser.Controller/Sync/SyncedFileInfo.cs @@ -1,8 +1,9 @@ using MediaBrowser.Model.MediaInfo; +using System.Collections.Generic; namespace MediaBrowser.Controller.Sync { - public class SendFileResult + public class SyncedFileInfo { /// /// Gets or sets the path. @@ -14,5 +15,15 @@ namespace MediaBrowser.Controller.Sync /// /// The protocol. public MediaProtocol Protocol { get; set; } + /// + /// Gets or sets the required HTTP headers. + /// + /// The required HTTP headers. + public Dictionary RequiredHttpHeaders { get; set; } + + public SyncedFileInfo() + { + RequiredHttpHeaders = new Dictionary(); + } } } diff --git a/MediaBrowser.Server.Implementations/Sync/MediaSync.cs b/MediaBrowser.Server.Implementations/Sync/MediaSync.cs index befddabd8a..03a7e92a45 100644 --- a/MediaBrowser.Server.Implementations/Sync/MediaSync.cs +++ b/MediaBrowser.Server.Implementations/Sync/MediaSync.cs @@ -301,7 +301,7 @@ namespace MediaBrowser.Server.Implementations.Sync } } - private async Task SendFile(IServerSyncProvider provider, string inputPath, string remotePath, SyncTarget target, IProgress progress, CancellationToken cancellationToken) + private async Task SendFile(IServerSyncProvider provider, string inputPath, string remotePath, SyncTarget target, IProgress progress, CancellationToken cancellationToken) { _logger.Debug("Sending {0} to {1}. Remote path: {2}", inputPath, provider.Name, remotePath); using (var stream = _fileSystem.GetFileStream(inputPath, FileMode.Open, FileAccess.Read, FileShare.Read, true)) diff --git a/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs b/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs index e7e1d1c633..4172cfc2da 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs @@ -78,7 +78,7 @@ namespace MediaBrowser.Server.Implementations.Sync SyncTarget target, CancellationToken cancellationToken) { - var requiresDynamicAccess = provider as IRequiresDynamicAccess; + var requiresDynamicAccess = provider as IHasDynamicAccess; if (requiresDynamicAccess == null) { @@ -88,11 +88,11 @@ namespace MediaBrowser.Server.Implementations.Sync try { - var dynamicInfo = await requiresDynamicAccess.GetFileInfo(item.LocalPath, target, cancellationToken).ConfigureAwait(false); + var dynamicInfo = await requiresDynamicAccess.GetSyncedFileInfo(item.LocalPath, target, cancellationToken).ConfigureAwait(false); foreach (var stream in mediaSource.MediaStreams) { - var dynamicStreamInfo = await requiresDynamicAccess.GetFileInfo(stream.ExternalId, target, cancellationToken).ConfigureAwait(false); + var dynamicStreamInfo = await requiresDynamicAccess.GetSyncedFileInfo(stream.ExternalId, target, cancellationToken).ConfigureAwait(false); stream.Path = dynamicStreamInfo.Path; } diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index 84187e72bc..31441ff791 100644 --- a/Nuget/MediaBrowser.Common.Internal.nuspec +++ b/Nuget/MediaBrowser.Common.Internal.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common.Internal - 3.0.602 + 3.0.603 MediaBrowser.Common.Internal Luke ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption. Copyright © Media Browser 2013 - + diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index 59ec2aa65b..568a47dfe0 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common - 3.0.602 + 3.0.603 MediaBrowser.Common Media Browser Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Model.Signed.nuspec b/Nuget/MediaBrowser.Model.Signed.nuspec index 6f4b6a7e39..fb00fd8407 100644 --- a/Nuget/MediaBrowser.Model.Signed.nuspec +++ b/Nuget/MediaBrowser.Model.Signed.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Model.Signed - 3.0.602 + 3.0.603 MediaBrowser.Model - Signed Edition Media Browser Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index 4d919a965e..c36fb6d6cd 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Server.Core - 3.0.602 + 3.0.603 Media Browser.Server.Core Media Browser Team ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains core components required to build plugins for Media Browser Server. Copyright © Media Browser 2013 - + From 3add1872c860cae14f85b339fef843ff962574aa Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 28 Mar 2015 14:55:00 -0400 Subject: [PATCH 13/52] make sure appVersion is set --- .../Session/SessionManager.cs | 11 +++++++++-- SharedVersion.cs | 4 ++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index f88e21aea3..201e86fe2f 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -1225,7 +1225,7 @@ namespace MediaBrowser.Server.Implementations.Session throw new UnauthorizedAccessException("Invalid user or password entered."); } - var token = await GetAuthorizationToken(user.Id.ToString("N"), request.DeviceId, request.App, request.DeviceName).ConfigureAwait(false); + var token = await GetAuthorizationToken(user.Id.ToString("N"), request.DeviceId, request.App, request.AppVersion, request.DeviceName).ConfigureAwait(false); EventHelper.FireEventIfNotNull(AuthenticationSucceeded, this, new GenericEventArgs(request), _logger); @@ -1246,7 +1246,7 @@ namespace MediaBrowser.Server.Implementations.Session }; } - private async Task GetAuthorizationToken(string userId, string deviceId, string app, string deviceName) + private async Task GetAuthorizationToken(string userId, string deviceId, string app, string appVersion, string deviceName) { var existing = _authRepo.Get(new AuthenticationInfoQuery { @@ -1265,6 +1265,7 @@ namespace MediaBrowser.Server.Implementations.Session var newToken = new AuthenticationInfo { AppName = app, + AppVersion = appVersion, DateCreated = DateTime.UtcNow, DeviceId = deviceId, DeviceName = deviceName, @@ -1690,6 +1691,12 @@ namespace MediaBrowser.Server.Implementations.Session deviceId = info.DeviceId; } + // Prevent argument exception + if (string.IsNullOrWhiteSpace(appVersion)) + { + appVersion = "1"; + } + return LogSessionActivity(appName, appVersion, deviceId, deviceName, remoteEndpoint, user); } diff --git a/SharedVersion.cs b/SharedVersion.cs index 621b97a63d..8019ca0e33 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,4 +1,4 @@ using System.Reflection; -//[assembly: AssemblyVersion("3.0.*")] -[assembly: AssemblyVersion("3.0.5557.30000")] +[assembly: AssemblyVersion("3.0.*")] +//[assembly: AssemblyVersion("3.0.5557.30000")] From bd2ea703e31522d505407a33089b95f997f6b062 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 28 Mar 2015 16:22:27 -0400 Subject: [PATCH 14/52] implement modular media sources --- .../Playback/BaseStreamingService.cs | 255 +++++------------- .../Playback/Dash/MpegDashService.cs | 6 +- .../Playback/Hls/BaseHlsService.cs | 6 +- .../Playback/Hls/DynamicHlsService.cs | 11 +- .../Playback/Hls/VideoHlsService.cs | 2 +- MediaBrowser.Api/Playback/MediaInfoService.cs | 108 +++++--- .../Playback/Progressive/AudioService.cs | 6 +- .../BaseProgressiveStreamingService.cs | 2 +- .../Playback/Progressive/VideoService.cs | 6 +- MediaBrowser.Api/Playback/StreamState.cs | 23 +- MediaBrowser.Api/Subtitles/SubtitleService.cs | 2 +- .../Channels/ChannelAudioItem.cs | 16 +- .../Channels/ChannelVideoItem.cs | 16 +- .../Channels/IChannelManager.cs | 6 +- .../Library/IMediaSourceManager.cs | 24 ++ .../Library/IMediaSourceProvider.cs | 16 ++ MediaBrowser.Controller/LiveTv/ILiveTvItem.cs | 4 +- .../LiveTv/LiveTvAudioRecording.cs | 21 +- .../LiveTv/LiveTvChannel.cs | 2 +- .../LiveTv/LiveTvVideoRecording.cs | 23 +- MediaBrowser.Dlna/PlayTo/PlayToController.cs | 2 +- MediaBrowser.Model/Dlna/StreamBuilder.cs | 24 +- MediaBrowser.Model/Dlna/SubtitleProfile.cs | 27 +- MediaBrowser.Model/Dto/MediaSourceInfo.cs | 5 + MediaBrowser.Model/Dto/MediaSourceType.cs | 2 +- MediaBrowser.Model/Entities/MediaStream.cs | 5 + .../MediaInfo/PlaybackInfoRequest.cs | 2 - .../MediaInfo/AudioImageProvider.cs | 2 +- .../Channels/ChannelDownloadScheduledTask.cs | 14 +- .../ChannelDynamicMediaSourceProvider.cs | 43 +++ .../Channels/ChannelManager.cs | 47 ++-- .../Library/MediaSourceManager.cs | 148 ++++++++-- .../LiveTv/LiveTvManager.cs | 9 +- .../LiveTv/LiveTvMediaSourceProvider.cs | 77 ++++++ ...MediaBrowser.Server.Implementations.csproj | 2 + .../Sync/MediaSync.cs | 1 + .../Sync/SyncedMediaSourceProvider.cs | 74 +++-- .../ApplicationHost.cs | 2 +- Nuget/MediaBrowser.Common.Internal.nuspec | 4 +- Nuget/MediaBrowser.Common.nuspec | 6 +- Nuget/MediaBrowser.Model.Signed.nuspec | 6 +- Nuget/MediaBrowser.Server.Core.nuspec | 6 +- 42 files changed, 697 insertions(+), 366 deletions(-) create mode 100644 MediaBrowser.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs create mode 100644 MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index bc194b45b0..435bda2c44 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -1,6 +1,5 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; -using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Dlna; @@ -65,7 +64,6 @@ namespace MediaBrowser.Api.Playback protected IFileSystem FileSystem { get; private set; } - protected ILiveTvManager LiveTvManager { get; private set; } protected IDlnaManager DlnaManager { get; private set; } protected IDeviceManager DeviceManager { get; private set; } protected ISubtitleEncoder SubtitleEncoder { get; private set; } @@ -75,14 +73,13 @@ namespace MediaBrowser.Api.Playback /// /// Initializes a new instance of the class. /// - protected BaseStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient) + protected BaseStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient) { ZipClient = zipClient; MediaSourceManager = mediaSourceManager; DeviceManager = deviceManager; SubtitleEncoder = subtitleEncoder; DlnaManager = dlnaManager; - LiveTvManager = liveTvManager; FileSystem = fileSystem; ServerConfigurationManager = serverConfig; UserManager = userManager; @@ -95,11 +92,10 @@ namespace MediaBrowser.Api.Playback /// Gets the command line arguments. /// /// The output path. - /// The transcoding job identifier. /// The state. /// if set to true [is encoding]. /// System.String. - protected abstract string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding); + protected abstract string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding); /// /// Gets the type of the transcoding job. @@ -128,7 +124,7 @@ namespace MediaBrowser.Api.Playback var outputFileExtension = GetOutputFileExtension(state); - var data = GetCommandLineArguments("dummy\\dummy", "dummyTranscodingId", state, false); + var data = GetCommandLineArguments("dummy\\dummy", state, false); data += "-" + (state.Request.DeviceId ?? string.Empty); data += "-" + (state.Request.StreamId ?? string.Empty); @@ -719,8 +715,10 @@ namespace MediaBrowser.Api.Playback seconds.ToString(UsCulture)); } + var mediaPath = state.MediaPath ?? string.Empty; + return string.Format("subtitles='{0}:si={1}',setpts=PTS -{2}/TB", - state.MediaPath.Replace('\\', '/').Replace(":/", "\\:/"), + mediaPath.Replace('\\', '/').Replace(":/", "\\:/"), state.InternalSubtitleStreamOffset.ToString(UsCulture), seconds.ToString(UsCulture)); } @@ -895,12 +893,11 @@ namespace MediaBrowser.Api.Playback /// /// Gets the input argument. /// - /// The transcoding job identifier. /// The state. /// System.String. - protected string GetInputArgument(string transcodingJobId, StreamState state) + protected string GetInputArgument(StreamState state) { - var arg = "-i " + GetInputPathArgument(transcodingJobId, state); + var arg = "-i " + GetInputPathArgument(state); if (state.SubtitleStream != null) { @@ -913,27 +910,18 @@ namespace MediaBrowser.Api.Playback return arg; } - private string GetInputPathArgument(string transcodingJobId, StreamState state) + private string GetInputPathArgument(StreamState state) { - //if (state.InputProtocol == MediaProtocol.File && - // state.RunTimeTicks.HasValue && - // state.VideoType == VideoType.VideoFile && - // !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) - //{ - // if (state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks && state.IsInputVideo) - // { - // } - //} - var protocol = state.InputProtocol; + var mediaPath = state.MediaPath ?? string.Empty; - var inputPath = new[] { state.MediaPath }; + var inputPath = new[] { mediaPath }; if (state.IsInputVideo) { if (!(state.VideoType == VideoType.Iso && state.IsoMount == null)) { - inputPath = MediaEncoderHelpers.GetInputArgument(state.MediaPath, state.InputProtocol, state.IsoMount, state.PlayableStreamFileNames); + inputPath = MediaEncoderHelpers.GetInputArgument(mediaPath, state.InputProtocol, state.IsoMount, state.PlayableStreamFileNames); } } @@ -947,55 +935,20 @@ namespace MediaBrowser.Api.Playback state.IsoMount = await IsoManager.Mount(state.MediaPath, cancellationTokenSource.Token).ConfigureAwait(false); } - if (string.IsNullOrEmpty(state.MediaPath)) + if (state.MediaSource.RequiresOpening) { - var checkCodecs = false; - - if (string.Equals(state.ItemType, typeof(LiveTvChannel).Name)) - { - var streamInfo = await LiveTvManager.GetChannelStream(state.Request.Id, cancellationTokenSource.Token).ConfigureAwait(false); + var mediaSource = await MediaSourceManager.OpenMediaSource(state.MediaSource.OpenKey, cancellationTokenSource.Token) + .ConfigureAwait(false); - state.LiveTvStreamId = streamInfo.Id; + AttachMediaSourceInfo(state, mediaSource, state.VideoRequest, state.RequestedUrl); - state.MediaPath = streamInfo.Path; - state.InputProtocol = streamInfo.Protocol; - - await Task.Delay(1500, cancellationTokenSource.Token).ConfigureAwait(false); - - AttachMediaStreamInfo(state, streamInfo, state.VideoRequest, state.RequestedUrl); - checkCodecs = true; - } - - else if (string.Equals(state.ItemType, typeof(LiveTvVideoRecording).Name) || - string.Equals(state.ItemType, typeof(LiveTvAudioRecording).Name)) + if (state.VideoRequest != null) { - var streamInfo = await LiveTvManager.GetRecordingStream(state.Request.Id, cancellationTokenSource.Token).ConfigureAwait(false); - - state.LiveTvStreamId = streamInfo.Id; - - state.MediaPath = streamInfo.Path; - state.InputProtocol = streamInfo.Protocol; - - await Task.Delay(1500, cancellationTokenSource.Token).ConfigureAwait(false); - - AttachMediaStreamInfo(state, streamInfo, state.VideoRequest, state.RequestedUrl); - checkCodecs = true; + TryStreamCopy(state, state.VideoRequest); } - var videoRequest = state.VideoRequest; - - if (videoRequest != null && checkCodecs) - { - if (state.VideoStream != null && CanStreamCopyVideo(videoRequest, state.VideoStream)) - { - state.OutputVideoCodec = "copy"; - } - - if (state.AudioStream != null && CanStreamCopyAudio(videoRequest, state.AudioStream, state.SupportedAudioCodecs)) - { - state.OutputAudioCodec = "copy"; - } - } + // TODO: This is only needed for live tv + await Task.Delay(1500, cancellationTokenSource.Token).ConfigureAwait(false); } } @@ -1017,7 +970,7 @@ namespace MediaBrowser.Api.Playback await AcquireResources(state, cancellationTokenSource).ConfigureAwait(false); var transcodingId = Guid.NewGuid().ToString("N"); - var commandLineArgs = GetCommandLineArguments(outputPath, transcodingId, state, true); + var commandLineArgs = GetCommandLineArguments(outputPath, state, true); if (ApiEntryPoint.Instance.GetEncodingOptions().EnableDebugLogging) { @@ -1644,7 +1597,7 @@ namespace MediaBrowser.Api.Playback request.AudioCodec = InferAudioCodec(url); } - var state = new StreamState(LiveTvManager, Logger) + var state = new StreamState(MediaSourceManager, Logger) { Request = request, RequestedUrl = url @@ -1658,109 +1611,20 @@ namespace MediaBrowser.Api.Playback var item = LibraryManager.GetItemById(request.Id); - List mediaStreams = null; + state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase); - state.ItemType = item.GetType().Name; - state.ItemId = item.Id.ToString("N"); var archivable = item as IArchivable; state.IsInputArchive = archivable != null && archivable.IsArchive; - if (item is ILiveTvRecording) - { - var recording = await LiveTvManager.GetInternalRecording(request.Id, cancellationToken).ConfigureAwait(false); - - state.VideoType = VideoType.VideoFile; - state.IsInputVideo = string.Equals(recording.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase); - - var path = recording.RecordingInfo.Path; - var mediaUrl = recording.RecordingInfo.Url; - - var source = string.IsNullOrEmpty(request.MediaSourceId) - ? recording.GetMediaSources(false).First() - : MediaSourceManager.GetStaticMediaSource(recording, request.MediaSourceId, false); - - mediaStreams = source.MediaStreams; - - // Just to prevent this from being null and causing other methods to fail - state.MediaPath = string.Empty; - - if (!string.IsNullOrEmpty(path)) - { - state.MediaPath = path; - state.InputProtocol = MediaProtocol.File; - } - else if (!string.IsNullOrEmpty(mediaUrl)) - { - state.MediaPath = mediaUrl; - state.InputProtocol = MediaProtocol.Http; - } - - state.RunTimeTicks = recording.RunTimeTicks; - state.DeInterlace = true; - state.OutputAudioSync = "1000"; - state.InputVideoSync = "-1"; - state.InputAudioSync = "1"; - state.InputContainer = recording.Container; - state.ReadInputAtNativeFramerate = source.ReadAtNativeFramerate; - } - else if (item is LiveTvChannel) - { - var channel = LiveTvManager.GetInternalChannel(request.Id); - - state.VideoType = VideoType.VideoFile; - state.IsInputVideo = string.Equals(channel.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase); - mediaStreams = new List(); - - state.DeInterlace = true; - - // Just to prevent this from being null and causing other methods to fail - state.MediaPath = string.Empty; - } - else - { - var mediaSources = await MediaSourceManager.GetPlayackMediaSources(request.Id, false, cancellationToken).ConfigureAwait(false); - - var mediaSource = string.IsNullOrEmpty(request.MediaSourceId) - ? mediaSources.First() - : mediaSources.First(i => string.Equals(i.Id, request.MediaSourceId)); - - mediaStreams = mediaSource.MediaStreams; - - state.MediaPath = mediaSource.Path; - state.InputProtocol = mediaSource.Protocol; - state.InputContainer = mediaSource.Container; - state.InputFileSize = mediaSource.Size; - state.InputBitrate = mediaSource.Bitrate; - state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate; - state.RunTimeTicks = mediaSource.RunTimeTicks; - state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders; - - var video = item as Video; - - if (video != null) - { - state.IsInputVideo = true; - - if (mediaSource.VideoType.HasValue) - { - state.VideoType = mediaSource.VideoType.Value; - } - - state.IsoType = mediaSource.IsoType; - - state.PlayableStreamFileNames = mediaSource.PlayableStreamFileNames.ToList(); - - if (mediaSource.Timestamp.HasValue) - { - state.InputTimestamp = mediaSource.Timestamp.Value; - } - } - - } + var mediaSources = await MediaSourceManager.GetPlayackMediaSources(request.Id, false, cancellationToken).ConfigureAwait(false); + var mediaSource = string.IsNullOrEmpty(request.MediaSourceId) + ? mediaSources.First() + : mediaSources.First(i => string.Equals(i.Id, request.MediaSourceId)); + var videoRequest = request as VideoStreamRequest; - AttachMediaStreamInfo(state, mediaStreams, videoRequest, url); + AttachMediaSourceInfo(state, mediaSource, videoRequest, url); var container = Path.GetExtension(state.RequestedUrl); @@ -1801,15 +1665,7 @@ namespace MediaBrowser.Api.Playback if (videoRequest != null) { - if (state.VideoStream != null && CanStreamCopyVideo(videoRequest, state.VideoStream)) - { - state.OutputVideoCodec = "copy"; - } - - if (state.AudioStream != null && CanStreamCopyAudio(videoRequest, state.AudioStream, state.SupportedAudioCodecs)) - { - state.OutputAudioCodec = "copy"; - } + TryStreamCopy(state, videoRequest); } state.OutputFilePath = GetOutputFilePath(state); @@ -1817,11 +1673,47 @@ namespace MediaBrowser.Api.Playback return state; } - private void AttachMediaStreamInfo(StreamState state, + private void TryStreamCopy(StreamState state, VideoStreamRequest videoRequest) + { + if (state.VideoStream != null && CanStreamCopyVideo(videoRequest, state.VideoStream)) + { + state.OutputVideoCodec = "copy"; + } + + if (state.AudioStream != null && CanStreamCopyAudio(videoRequest, state.AudioStream, state.SupportedAudioCodecs)) + { + state.OutputAudioCodec = "copy"; + } + } + + private void AttachMediaSourceInfo(StreamState state, MediaSourceInfo mediaSource, VideoStreamRequest videoRequest, string requestedUrl) { + state.MediaPath = mediaSource.Path; + state.InputProtocol = mediaSource.Protocol; + state.InputContainer = mediaSource.Container; + state.InputFileSize = mediaSource.Size; + state.InputBitrate = mediaSource.Bitrate; + state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate; + state.RunTimeTicks = mediaSource.RunTimeTicks; + state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders; + + if (mediaSource.VideoType.HasValue) + { + state.VideoType = mediaSource.VideoType.Value; + } + + state.IsoType = mediaSource.IsoType; + + state.PlayableStreamFileNames = mediaSource.PlayableStreamFileNames.ToList(); + + if (mediaSource.Timestamp.HasValue) + { + state.InputTimestamp = mediaSource.Timestamp.Value; + } + state.InputProtocol = mediaSource.Protocol; state.MediaPath = mediaSource.Path; state.RunTimeTicks = mediaSource.RunTimeTicks; @@ -1830,21 +1722,16 @@ namespace MediaBrowser.Api.Playback state.InputFileSize = mediaSource.Size; state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate; - if (state.ReadInputAtNativeFramerate) + if (state.ReadInputAtNativeFramerate || + mediaSource.Protocol == MediaProtocol.File && string.Equals(mediaSource.Container, "wtv", StringComparison.OrdinalIgnoreCase)) { state.OutputAudioSync = "1000"; state.InputVideoSync = "-1"; state.InputAudioSync = "1"; } - AttachMediaStreamInfo(state, mediaSource.MediaStreams, videoRequest, requestedUrl); - } + var mediaStreams = mediaSource.MediaStreams; - private void AttachMediaStreamInfo(StreamState state, - List mediaStreams, - VideoStreamRequest videoRequest, - string requestedUrl) - { if (videoRequest != null) { if (string.IsNullOrEmpty(videoRequest.VideoCodec)) @@ -1873,7 +1760,7 @@ namespace MediaBrowser.Api.Playback state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true); } - state.AllMediaStreams = mediaStreams; + state.MediaSource = mediaSource; } private bool CanStreamCopyVideo(VideoStreamRequest request, MediaStream videoStream) diff --git a/MediaBrowser.Api/Playback/Dash/MpegDashService.cs b/MediaBrowser.Api/Playback/Dash/MpegDashService.cs index 3c3f4c0397..692e8d4e74 100644 --- a/MediaBrowser.Api/Playback/Dash/MpegDashService.cs +++ b/MediaBrowser.Api/Playback/Dash/MpegDashService.cs @@ -54,7 +54,7 @@ namespace MediaBrowser.Api.Playback.Dash public class MpegDashService : BaseHlsService { - public MpegDashService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, INetworkManager networkManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient) + public MpegDashService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, INetworkManager networkManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient) { NetworkManager = networkManager; } @@ -447,7 +447,7 @@ namespace MediaBrowser.Api.Playback.Dash return args; } - protected override string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding) + protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding) { // test url http://192.168.1.2:8096/videos/233e8905d559a8f230db9bffd2ac9d6d/master.mpd?mediasourceid=233e8905d559a8f230db9bffd2ac9d6d&videocodec=h264&audiocodec=aac&maxwidth=1280&videobitrate=500000&audiobitrate=128000&profile=baseline&level=3 // Good info on i-frames http://blog.streamroot.io/encode-multi-bitrate-videos-mpeg-dash-mse-based-media-players/ @@ -461,7 +461,7 @@ namespace MediaBrowser.Api.Playback.Dash var args = string.Format("{0} {1} -map_metadata -1 -threads {2} {3} {4} -copyts {5} -f dash -init_seg_name \"{6}\" -media_seg_name \"{7}\" -use_template 0 -use_timeline 1 -min_seg_duration {8} -y \"{9}\"", inputModifier, - GetInputArgument(transcodingJobId, state), + GetInputArgument(state), threads, GetMapArgs(state), GetVideoArguments(state), diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index 09574e772c..207bc2f679 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -22,7 +22,7 @@ namespace MediaBrowser.Api.Playback.Hls /// public abstract class BaseHlsService : BaseStreamingService { - protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient) + protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient) { } @@ -212,7 +212,7 @@ namespace MediaBrowser.Api.Playback.Hls } } - protected override string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding) + protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding) { var hlsVideoRequest = state.VideoRequest as GetHlsVideoStream; @@ -240,7 +240,7 @@ namespace MediaBrowser.Api.Playback.Hls var args = string.Format("{0} {1} {2} -map_metadata -1 -threads {3} {4} {5} -sc_threshold 0 {6} -hls_time {7} -start_number {8} -hls_list_size {9}{10} -y \"{11}\"", itsOffset, inputModifier, - GetInputArgument(transcodingJobId, state), + GetInputArgument(state), threads, GetMapArgs(state), GetVideoArguments(state), diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index 48523e2556..b166bc319f 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -62,7 +62,7 @@ namespace MediaBrowser.Api.Playback.Hls public class DynamicHlsService : BaseHlsService { - public DynamicHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, INetworkManager networkManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient) + public DynamicHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, INetworkManager networkManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient) { NetworkManager = networkManager; } @@ -414,7 +414,8 @@ namespace MediaBrowser.Api.Playback.Hls var request = (GetMasterHlsVideoStream)state.Request; - var subtitleStreams = state.AllMediaStreams + var subtitleStreams = state.MediaSource + .MediaStreams .Where(i => i.IsTextSubtitleStream) .ToList(); @@ -684,7 +685,7 @@ namespace MediaBrowser.Api.Playback.Hls return args; } - protected override string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding) + protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding) { var threads = GetNumberOfThreads(state, false); @@ -699,7 +700,7 @@ namespace MediaBrowser.Api.Playback.Hls return string.Format("{0} {1} -map_metadata -1 -threads {2} {3} {4} -copyts -flags -global_header {5} -f segment -segment_time {6} -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"", inputModifier, - GetInputArgument(transcodingJobId, state), + GetInputArgument(state), threads, GetMapArgs(state), GetVideoArguments(state), @@ -713,7 +714,7 @@ namespace MediaBrowser.Api.Playback.Hls return string.Format("{0} {1} -map_metadata -1 -threads {2} {3} {4} -copyts -flags -global_header {5} -hls_time {6} -start_number {7} -hls_list_size {8} -y \"{9}\"", inputModifier, - GetInputArgument(transcodingJobId, state), + GetInputArgument(state), threads, GetMapArgs(state), GetVideoArguments(state), diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs index e1baf8c121..b1964f4aef 100644 --- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs @@ -42,7 +42,7 @@ namespace MediaBrowser.Api.Playback.Hls /// public class VideoHlsService : BaseHlsService { - public VideoHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient) + public VideoHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient) { } diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs index e219f41869..cef8a34e57 100644 --- a/MediaBrowser.Api/Playback/MediaInfoService.cs +++ b/MediaBrowser.Api/Playback/MediaInfoService.cs @@ -4,6 +4,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Session; using ServiceStack; @@ -38,20 +39,23 @@ namespace MediaBrowser.Api.Playback [Route("/Items/{Id}/PlaybackInfo", "POST", Summary = "Gets live playback media info for an item")] public class GetPostedPlaybackInfo : PlaybackInfoRequest, IReturn { - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public string Id { get; set; } - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] + [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] public string UserId { get; set; } - [ApiMember(Name = "StartTimeTicks", Description = "Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + [ApiMember(Name = "StartTimeTicks", Description = "Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")] public long? StartTimeTicks { get; set; } - [ApiMember(Name = "AudioStreamIndex", Description = "Optional. The index of the audio stream to use. If omitted the first audio stream will be used.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + [ApiMember(Name = "AudioStreamIndex", Description = "Optional. The index of the audio stream to use. If omitted the first audio stream will be used.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")] public int? AudioStreamIndex { get; set; } - [ApiMember(Name = "SubtitleStreamIndex", Description = "Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + [ApiMember(Name = "SubtitleStreamIndex", Description = "Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")] public int? SubtitleStreamIndex { get; set; } + + [ApiMember(Name = "MediaSourceId", Description = "The media version id, if playing an alternate version", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string MediaSourceId { get; set; } } [Authenticated] @@ -82,7 +86,7 @@ namespace MediaBrowser.Api.Playback public async Task Post(GetPostedPlaybackInfo request) { - var info = await GetPlaybackInfo(request.Id, request.UserId, request.MediaSource).ConfigureAwait(false); + var info = await GetPlaybackInfo(request.Id, request.UserId, request.MediaSourceId).ConfigureAwait(false); var authInfo = AuthorizationContext.GetAuthorizationInfo(Request); var profile = request.DeviceProfile; @@ -97,36 +101,36 @@ namespace MediaBrowser.Api.Playback if (profile != null) { - var mediaSourceId = request.MediaSource == null ? null : request.MediaSource.Id; + var mediaSourceId = request.MediaSourceId; SetDeviceSpecificData(request.Id, info, profile, authInfo, null, request.StartTimeTicks ?? 0, mediaSourceId, request.AudioStreamIndex, request.SubtitleStreamIndex); } return ToOptimizedResult(info); } - private async Task GetPlaybackInfo(string id, string userId, MediaSourceInfo mediaSource = null) + private async Task GetPlaybackInfo(string id, string userId, string mediaSourceId = null) { var result = new PlaybackInfoResponse(); - if (mediaSource == null) + IEnumerable mediaSources; + + try { - IEnumerable mediaSources; + mediaSources = await _mediaSourceManager.GetPlayackMediaSources(id, userId, true, CancellationToken.None).ConfigureAwait(false); + } + catch (PlaybackException ex) + { + mediaSources = new List(); + result.ErrorCode = ex.ErrorCode; + } - try - { - mediaSources = await _mediaSourceManager.GetPlayackMediaSources(id, userId, true, CancellationToken.None).ConfigureAwait(false); - } - catch (PlaybackException ex) - { - mediaSources = new List(); - result.ErrorCode = ex.ErrorCode; - } + result.MediaSources = mediaSources.ToList(); - result.MediaSources = mediaSources.ToList(); - } - else + if (!string.IsNullOrWhiteSpace(mediaSourceId)) { - result.MediaSources = new List { mediaSource }; + result.MediaSources = result.MediaSources + .Where(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase)) + .ToList(); } if (result.MediaSources.Count == 0) @@ -185,9 +189,9 @@ namespace MediaBrowser.Api.Playback mediaSource.SupportsDirectStream = true; // The MediaSource supports direct stream, now test to see if the client supports it - var streamInfo = item is Video ? - streamBuilder.BuildVideoItem(options) : - streamBuilder.BuildAudioItem(options); + var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ? + streamBuilder.BuildAudioItem(options) : + streamBuilder.BuildVideoItem(options); if (streamInfo == null || !streamInfo.IsDirectStream) { @@ -201,9 +205,9 @@ namespace MediaBrowser.Api.Playback if (mediaSource.SupportsDirectStream) { // The MediaSource supports direct stream, now test to see if the client supports it - var streamInfo = item is Video ? - streamBuilder.BuildVideoItem(options) : - streamBuilder.BuildAudioItem(options); + var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ? + streamBuilder.BuildAudioItem(options) : + streamBuilder.BuildVideoItem(options); if (streamInfo == null || !streamInfo.IsDirectStream) { @@ -214,9 +218,9 @@ namespace MediaBrowser.Api.Playback if (mediaSource.SupportsTranscoding) { // The MediaSource supports direct stream, now test to see if the client supports it - var streamInfo = item is Video ? - streamBuilder.BuildVideoItem(options) : - streamBuilder.BuildAudioItem(options); + var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ? + streamBuilder.BuildAudioItem(options) : + streamBuilder.BuildVideoItem(options); if (streamInfo != null && streamInfo.PlayMethod == PlayMethod.Transcode) { @@ -227,6 +231,46 @@ namespace MediaBrowser.Api.Playback } } } + + SortMediaSources(result); + } + + private void SortMediaSources(PlaybackInfoResponse result) + { + var originalList = result.MediaSources.ToList(); + + result.MediaSources = result.MediaSources.OrderBy(i => + { + // Nothing beats direct playing a file + if (i.SupportsDirectPlay && i.Protocol == MediaProtocol.File) + { + return 0; + } + + return 1; + + }).ThenBy(i => + { + // Let's assume direct streaming a file is just as desirable as direct playing a remote url + if (i.SupportsDirectPlay || i.SupportsDirectStream) + { + return 0; + } + + return 1; + + }).ThenBy(i => + { + switch (i.Protocol) + { + case MediaProtocol.File: + return 0; + default: + return 1; + } + + }).ThenBy(originalList.IndexOf) + .ToList(); } } } diff --git a/MediaBrowser.Api/Playback/Progressive/AudioService.cs b/MediaBrowser.Api/Playback/Progressive/AudioService.cs index af2cf6b038..fee5011596 100644 --- a/MediaBrowser.Api/Playback/Progressive/AudioService.cs +++ b/MediaBrowser.Api/Playback/Progressive/AudioService.cs @@ -31,7 +31,7 @@ namespace MediaBrowser.Api.Playback.Progressive /// public class AudioService : BaseProgressiveStreamingService { - public AudioService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, imageProcessor, httpClient) + public AudioService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, imageProcessor, httpClient) { } @@ -55,7 +55,7 @@ namespace MediaBrowser.Api.Playback.Progressive return ProcessRequest(request, true); } - protected override string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding) + protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding) { var audioTranscodeParams = new List(); @@ -84,7 +84,7 @@ namespace MediaBrowser.Api.Playback.Progressive return string.Format("{0} {1} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1 -y \"{5}\"", inputModifier, - GetInputArgument(transcodingJobId, state), + GetInputArgument(state), threads, vn, string.Join(" ", audioTranscodeParams.ToArray()), diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index 7a2990e2af..8ed17ca17e 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -27,7 +27,7 @@ namespace MediaBrowser.Api.Playback.Progressive protected readonly IImageProcessor ImageProcessor; protected readonly IHttpClient HttpClient; - protected BaseProgressiveStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient) + protected BaseProgressiveStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient) { ImageProcessor = imageProcessor; HttpClient = httpClient; diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs index eb18288e98..540c39a0c7 100644 --- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs +++ b/MediaBrowser.Api/Playback/Progressive/VideoService.cs @@ -62,7 +62,7 @@ namespace MediaBrowser.Api.Playback.Progressive /// public class VideoService : BaseProgressiveStreamingService { - public VideoService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, imageProcessor, httpClient) + public VideoService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, imageProcessor, httpClient) { } @@ -86,7 +86,7 @@ namespace MediaBrowser.Api.Playback.Progressive return ProcessRequest(request, true); } - protected override string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding) + protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding) { // Get the output codec name var videoCodec = state.OutputVideoCodec; @@ -106,7 +106,7 @@ namespace MediaBrowser.Api.Playback.Progressive return string.Format("{0} {1}{2} {3} {4} -map_metadata -1 -threads {5} {6}{7} -y \"{8}\"", inputModifier, - GetInputArgument(transcodingJobId, state), + GetInputArgument(state), keyFrame, GetMapArgs(state), GetVideoArguments(state, videoCodec), diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs index 1d4dd1aafb..37f2c7702c 100644 --- a/MediaBrowser.Api/Playback/StreamState.cs +++ b/MediaBrowser.Api/Playback/StreamState.cs @@ -1,6 +1,7 @@ -using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.Library; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Drawing; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; @@ -17,7 +18,7 @@ namespace MediaBrowser.Api.Playback public class StreamState : IDisposable { private readonly ILogger _logger; - private readonly ILiveTvManager _liveTvManager; + private readonly IMediaSourceManager _mediaSourceManager; public string RequestedUrl { get; set; } @@ -39,7 +40,7 @@ namespace MediaBrowser.Api.Playback public string InputContainer { get; set; } - public List AllMediaStreams { get; set; } + public MediaSourceInfo MediaSource { get; set; } public MediaStream AudioStream { get; set; } public MediaStream VideoStream { get; set; } @@ -64,8 +65,6 @@ namespace MediaBrowser.Api.Playback public List PlayableStreamFileNames { get; set; } - public string LiveTvStreamId { get; set; } - public int SegmentLength = 3; public bool EnableGenericHlsSegmenter = false; public int HlsListSize @@ -86,14 +85,13 @@ namespace MediaBrowser.Api.Playback public List SupportedAudioCodecs { get; set; } - public StreamState(ILiveTvManager liveTvManager, ILogger logger) + public StreamState(IMediaSourceManager mediaSourceManager, ILogger logger) { - _liveTvManager = liveTvManager; + _mediaSourceManager = mediaSourceManager; _logger = logger; SupportedAudioCodecs = new List(); PlayableStreamFileNames = new List(); RemoteHttpHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); - AllMediaStreams = new List(); } public string InputAudioSync { get; set; } @@ -113,9 +111,6 @@ namespace MediaBrowser.Api.Playback public long? EncodingDurationTicks { get; set; } - public string ItemType { get; set; } - public string ItemId { get; set; } - public string GetMimeType(string outputPath) { if (!string.IsNullOrEmpty(MimeType)) @@ -187,15 +182,15 @@ namespace MediaBrowser.Api.Playback private async void DisposeLiveStream() { - if (!string.IsNullOrEmpty(LiveTvStreamId)) + if (MediaSource.RequiresClosing) { try { - await _liveTvManager.CloseLiveStream(LiveTvStreamId, CancellationToken.None).ConfigureAwait(false); + await _mediaSourceManager.CloseMediaSource(MediaSource.CloseKey, CancellationToken.None).ConfigureAwait(false); } catch (Exception ex) { - _logger.ErrorException("Error closing live tv stream", ex); + _logger.ErrorException("Error closing media source", ex); } } } diff --git a/MediaBrowser.Api/Subtitles/SubtitleService.cs b/MediaBrowser.Api/Subtitles/SubtitleService.cs index 07eb74e81b..73589d6777 100644 --- a/MediaBrowser.Api/Subtitles/SubtitleService.cs +++ b/MediaBrowser.Api/Subtitles/SubtitleService.cs @@ -192,7 +192,7 @@ namespace MediaBrowser.Api.Subtitles { var item = (Video)_libraryManager.GetItemById(new Guid(request.Id)); - var mediaSource = item.GetMediaSources(false) + var mediaSource = _mediaSourceManager.GetStaticMediaSources(item, false, null) .First(i => string.Equals(i.Id, request.MediaSourceId ?? request.Id)); var subtitleStream = mediaSource.MediaStreams diff --git a/MediaBrowser.Controller/Channels/ChannelAudioItem.cs b/MediaBrowser.Controller/Channels/ChannelAudioItem.cs index 91b2407bee..8d90246765 100644 --- a/MediaBrowser.Controller/Channels/ChannelAudioItem.cs +++ b/MediaBrowser.Controller/Channels/ChannelAudioItem.cs @@ -75,17 +75,23 @@ namespace MediaBrowser.Controller.Channels public override IEnumerable GetMediaSources(bool enablePathSubstitution) { - var list = base.GetMediaSources(enablePathSubstitution).ToList(); - - var sources = ChannelManager.GetChannelItemMediaSources(Id.ToString("N"), false, CancellationToken.None) - .Result.ToList(); + var sources = ChannelManager.GetStaticMediaSources(this, false, CancellationToken.None) + .Result.ToList(); if (sources.Count > 0) { return sources; } - list.InsertRange(0, sources); + var list = base.GetMediaSources(enablePathSubstitution).ToList(); + + foreach (var mediaSource in list) + { + if (string.IsNullOrWhiteSpace(mediaSource.Path)) + { + mediaSource.Type = MediaSourceType.Placeholder; + } + } return list; } diff --git a/MediaBrowser.Controller/Channels/ChannelVideoItem.cs b/MediaBrowser.Controller/Channels/ChannelVideoItem.cs index d7d4483cd1..8eec2021b5 100644 --- a/MediaBrowser.Controller/Channels/ChannelVideoItem.cs +++ b/MediaBrowser.Controller/Channels/ChannelVideoItem.cs @@ -90,17 +90,23 @@ namespace MediaBrowser.Controller.Channels public override IEnumerable GetMediaSources(bool enablePathSubstitution) { - var list = base.GetMediaSources(enablePathSubstitution).ToList(); - - var sources = ChannelManager.GetChannelItemMediaSources(Id.ToString("N"), false, CancellationToken.None) - .Result.ToList(); + var sources = ChannelManager.GetStaticMediaSources(this, false, CancellationToken.None) + .Result.ToList(); if (sources.Count > 0) { return sources; } - list.InsertRange(0, sources); + var list = base.GetMediaSources(enablePathSubstitution).ToList(); + + foreach (var mediaSource in list) + { + if (string.IsNullOrWhiteSpace(mediaSource.Path)) + { + mediaSource.Type = MediaSourceType.Placeholder; + } + } return list; } diff --git a/MediaBrowser.Controller/Channels/IChannelManager.cs b/MediaBrowser.Controller/Channels/IChannelManager.cs index 05015da37d..f5c4ab3733 100644 --- a/MediaBrowser.Controller/Channels/IChannelManager.cs +++ b/MediaBrowser.Controller/Channels/IChannelManager.cs @@ -112,11 +112,11 @@ namespace MediaBrowser.Controller.Channels /// /// Gets the channel item media sources. /// - /// The identifier. - /// if set to true [include dynamic sources]. + /// The item. + /// if set to true [include cached versions]. /// The cancellation token. /// Task{IEnumerable{MediaSourceInfo}}. - Task> GetChannelItemMediaSources(string id, bool includeDynamicSources, CancellationToken cancellationToken); + Task> GetStaticMediaSources(IChannelMediaItem item, bool includeCachedVersions, CancellationToken cancellationToken); /// /// Gets the channel folder. diff --git a/MediaBrowser.Controller/Library/IMediaSourceManager.cs b/MediaBrowser.Controller/Library/IMediaSourceManager.cs index c21fed6fc5..fda17aa279 100644 --- a/MediaBrowser.Controller/Library/IMediaSourceManager.cs +++ b/MediaBrowser.Controller/Library/IMediaSourceManager.cs @@ -64,6 +64,14 @@ namespace MediaBrowser.Controller.Library /// IEnumerable<MediaSourceInfo>. IEnumerable GetStaticMediaSources(IHasMediaSources item, bool enablePathSubstitution, User user); + /// + /// Gets the static media sources. + /// + /// The item. + /// if set to true [enable path substitution]. + /// IEnumerable<MediaSourceInfo>. + IEnumerable GetStaticMediaSources(IHasMediaSources item, bool enablePathSubstitution); + /// /// Gets the static media source. /// @@ -72,5 +80,21 @@ namespace MediaBrowser.Controller.Library /// if set to true [enable path substitution]. /// MediaSourceInfo. MediaSourceInfo GetStaticMediaSource(IHasMediaSources item, string mediaSourceId, bool enablePathSubstitution); + + /// + /// Opens the media source. + /// + /// The open key. + /// The cancellation token. + /// Task<MediaSourceInfo>. + Task OpenMediaSource(string openKey, CancellationToken cancellationToken); + + /// + /// Closes the media source. + /// + /// The close key. + /// The cancellation token. + /// Task. + Task CloseMediaSource(string closeKey, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Controller/Library/IMediaSourceProvider.cs b/MediaBrowser.Controller/Library/IMediaSourceProvider.cs index 461285d6cc..c5f5b54010 100644 --- a/MediaBrowser.Controller/Library/IMediaSourceProvider.cs +++ b/MediaBrowser.Controller/Library/IMediaSourceProvider.cs @@ -15,5 +15,21 @@ namespace MediaBrowser.Controller.Library /// The cancellation token. /// Task<IEnumerable<MediaSourceInfo>>. Task> GetMediaSources(IHasMediaSources item, CancellationToken cancellationToken); + + /// + /// Opens the media source. + /// + /// The open key. + /// The cancellation token. + /// Task<MediaSourceInfo>. + Task OpenMediaSource(string openKey, CancellationToken cancellationToken); + + /// + /// Closes the media source. + /// + /// The close key. + /// The cancellation token. + /// Task. + Task CloseMediaSource(string closeKey, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvItem.cs b/MediaBrowser.Controller/LiveTv/ILiveTvItem.cs index d3334e8ea0..6c277a2e19 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvItem.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvItem.cs @@ -1,8 +1,10 @@ - +using System; + namespace MediaBrowser.Controller.LiveTv { public interface ILiveTvItem { + Guid Id { get; } string ServiceName { get; set; } } } diff --git a/MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs b/MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs index 9815066efc..0dc296d5a5 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs @@ -1,10 +1,12 @@ -using System.Runtime.Serialization; -using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Users; +using System.Collections.Generic; using System.Linq; +using System.Runtime.Serialization; namespace MediaBrowser.Controller.LiveTv { @@ -99,5 +101,20 @@ namespace MediaBrowser.Controller.LiveTv { return user.Policy.EnableLiveTvManagement; } + + public override IEnumerable GetMediaSources(bool enablePathSubstitution) + { + var list = base.GetMediaSources(enablePathSubstitution).ToList(); + + foreach (var mediaSource in list) + { + if (string.IsNullOrWhiteSpace(mediaSource.Path)) + { + mediaSource.Type = MediaSourceType.Placeholder; + } + } + + return list; + } } } diff --git a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs index 75e418bcc9..1e13d8f3f4 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs @@ -127,7 +127,7 @@ namespace MediaBrowser.Controller.LiveTv Name = Name, Path = Path, RunTimeTicks = RunTimeTicks, - Type = MediaSourceType.Default + Type = MediaSourceType.Placeholder }; list.Add(info); diff --git a/MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs b/MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs index 207684d55d..3669f94403 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs @@ -1,9 +1,11 @@ -using System.Runtime.Serialization; -using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using System.Linq; using MediaBrowser.Model.Users; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; namespace MediaBrowser.Controller.LiveTv { @@ -97,5 +99,20 @@ namespace MediaBrowser.Controller.LiveTv { return user.Policy.EnableLiveTvManagement; } + + public override IEnumerable GetMediaSources(bool enablePathSubstitution) + { + var list = base.GetMediaSources(enablePathSubstitution).ToList(); + + foreach (var mediaSource in list) + { + if (string.IsNullOrWhiteSpace(mediaSource.Path)) + { + mediaSource.Type = MediaSourceType.Placeholder; + } + } + + return list; + } } } diff --git a/MediaBrowser.Dlna/PlayTo/PlayToController.cs b/MediaBrowser.Dlna/PlayTo/PlayToController.cs index f8f939f479..17385bda62 100644 --- a/MediaBrowser.Dlna/PlayTo/PlayToController.cs +++ b/MediaBrowser.Dlna/PlayTo/PlayToController.cs @@ -476,7 +476,7 @@ namespace MediaBrowser.Dlna.PlayTo var playlistItem = GetPlaylistItem(item, mediaSources, profile, _session.DeviceId, mediaSourceId, audioStreamIndex, subtitleStreamIndex); playlistItem.StreamInfo.StartPositionTicks = startPostionTicks; - playlistItem.StreamUrl = playlistItem.StreamInfo.ToUrl(_serverAddress, _accessToken); + playlistItem.StreamUrl = playlistItem.StreamInfo.ToDlnaUrl(_serverAddress, _accessToken); var itemXml = new DidlBuilder(profile, user, _imageProcessor, _serverAddress, _accessToken, _userDataManager, _localization, _mediaSourceManager) .GetItemDidl(item, null, _session.DeviceId, new Filter(), playlistItem.StreamInfo); diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 62ac321fe3..6534eda10c 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -239,6 +239,16 @@ namespace MediaBrowser.Model.Dlna return playlistItem; } + private int? GetBitrateForDirectPlayCheck(MediaSourceInfo item, AudioOptions options) + { + if (item.Protocol == MediaProtocol.File) + { + return options.Profile.MaxStaticBitrate; + } + + return options.GetMaxBitrate(); + } + private List GetAudioDirectPlayMethods(MediaSourceInfo item, MediaStream audioStream, AudioOptions options) { DirectPlayProfile directPlayProfile = null; @@ -263,7 +273,7 @@ namespace MediaBrowser.Model.Dlna // The profile describes what the device supports // If device requirements are satisfied then allow both direct stream and direct play - if (IsAudioEligibleForDirectPlay(item, options.Profile.MaxStaticBitrate)) + if (IsAudioEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options))) { playMethods.Add(PlayMethod.DirectPlay); } @@ -293,7 +303,7 @@ namespace MediaBrowser.Model.Dlna MediaStream videoStream = item.VideoStream; // TODO: This doesn't accout for situation of device being able to handle media bitrate, but wifi connection not fast enough - bool isEligibleForDirectPlay = IsEligibleForDirectPlay(item, options.Profile.MaxStaticBitrate, subtitleStream, options); + bool isEligibleForDirectPlay = IsEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options), subtitleStream, options); bool isEligibleForDirectStream = IsEligibleForDirectPlay(item, options.GetMaxBitrate(), subtitleStream, options); if (isEligibleForDirectPlay || isEligibleForDirectStream) @@ -604,6 +614,11 @@ namespace MediaBrowser.Model.Dlna // Look for an external profile that matches the stream type (text/graphical) foreach (SubtitleProfile profile in subtitleProfiles) { + if (!profile.SupportsLanguage(subtitleStream.Language)) + { + continue; + } + if (profile.Method == SubtitleDeliveryMethod.External && subtitleStream.IsTextSubtitleStream == MediaStream.IsTextFormat(profile.Format)) { if (subtitleStream.SupportsExternalStream) @@ -621,6 +636,11 @@ namespace MediaBrowser.Model.Dlna foreach (SubtitleProfile profile in subtitleProfiles) { + if (!profile.SupportsLanguage(subtitleStream.Language)) + { + continue; + } + if (profile.Method == SubtitleDeliveryMethod.Embed && subtitleStream.IsTextSubtitleStream == MediaStream.IsTextFormat(profile.Format)) { return profile; diff --git a/MediaBrowser.Model/Dlna/SubtitleProfile.cs b/MediaBrowser.Model/Dlna/SubtitleProfile.cs index d3989829ca..1795c374a4 100644 --- a/MediaBrowser.Model/Dlna/SubtitleProfile.cs +++ b/MediaBrowser.Model/Dlna/SubtitleProfile.cs @@ -1,4 +1,6 @@ -using System.Xml.Serialization; +using MediaBrowser.Model.Extensions; +using System.Collections.Generic; +using System.Xml.Serialization; namespace MediaBrowser.Model.Dlna { @@ -13,5 +15,28 @@ namespace MediaBrowser.Model.Dlna [XmlAttribute("didlMode")] public string DidlMode { get; set; } + [XmlAttribute("language")] + public string Language { get; set; } + + public List GetLanguages() + { + List list = new List(); + foreach (string i in (Language ?? string.Empty).Split(',')) + { + if (!string.IsNullOrEmpty(i)) list.Add(i); + } + return list; + } + + public bool SupportsLanguage(string language) + { + if (string.IsNullOrEmpty(language)) + { + language = "und"; + } + + List languages = GetLanguages(); + return languages.Count == 0 || ListHelper.ContainsIgnoreCase(languages, language); + } } } \ No newline at end of file diff --git a/MediaBrowser.Model/Dto/MediaSourceInfo.cs b/MediaBrowser.Model/Dto/MediaSourceInfo.cs index 31d310acda..92af8d671c 100644 --- a/MediaBrowser.Model/Dto/MediaSourceInfo.cs +++ b/MediaBrowser.Model/Dto/MediaSourceInfo.cs @@ -26,6 +26,11 @@ namespace MediaBrowser.Model.Dto public bool SupportsDirectStream { get; set; } public bool SupportsDirectPlay { get; set; } + public bool RequiresOpening { get; set; } + public string OpenKey { get; set; } + public bool RequiresClosing { get; set; } + public string CloseKey { get; set; } + public VideoType? VideoType { get; set; } public IsoType? IsoType { get; set; } diff --git a/MediaBrowser.Model/Dto/MediaSourceType.cs b/MediaBrowser.Model/Dto/MediaSourceType.cs index a9cd71df56..e049785025 100644 --- a/MediaBrowser.Model/Dto/MediaSourceType.cs +++ b/MediaBrowser.Model/Dto/MediaSourceType.cs @@ -4,6 +4,6 @@ namespace MediaBrowser.Model.Dto { Default = 0, Grouping = 1, - Cache = 2 + Placeholder = 2 } } \ No newline at end of file diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index 66fb486284..fa075490a5 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -141,6 +141,11 @@ namespace MediaBrowser.Model.Entities { if (Type != MediaStreamType.Subtitle) return false; + if (string.IsNullOrEmpty(Codec) && !IsExternal) + { + return false; + } + return IsTextFormat(Codec); } } diff --git a/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs b/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs index 783fb41203..ffd4995ad9 100644 --- a/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs +++ b/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs @@ -1,11 +1,9 @@ using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Dto; namespace MediaBrowser.Model.MediaInfo { public class PlaybackInfoRequest { public DeviceProfile DeviceProfile { get; set; } - public MediaSourceInfo MediaSource { get; set; } } } diff --git a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs index 65d8e287f9..99be102f85 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs @@ -96,7 +96,7 @@ namespace MediaBrowser.Providers.MediaInfo var album = item.Parent as MusicAlbum; var filename = item.Album ?? string.Empty; - filename += item.Artists.FirstOrDefault() ?? string.Empty; + filename += string.Join(",", item.Artists.ToArray()); filename += album == null ? item.Id.ToString("N") + "_primary" + item.DateModified.Ticks : album.Id.ToString("N") + album.DateModified.Ticks + "_primary"; filename = filename.GetMD5() + ".jpg"; diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs b/MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs index e0b616605d..980c3f31b7 100644 --- a/MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs +++ b/MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs @@ -169,7 +169,7 @@ namespace MediaBrowser.Server.Implementations.Channels foreach (var item in result.Items) { - var channelItem = (IChannelItem)item; + var channelItem = (IChannelMediaItem)item; var channelFeatures = _manager.GetChannelFeatures(channelItem.ChannelId); @@ -179,7 +179,7 @@ namespace MediaBrowser.Server.Implementations.Channels { try { - await DownloadChannelItem(item, options, cancellationToken, path); + await DownloadChannelItem(channelItem, options, cancellationToken, path); } catch (OperationCanceledException) { @@ -210,13 +210,13 @@ namespace MediaBrowser.Server.Implementations.Channels return channelOptions.DownloadSizeLimit; } - private async Task DownloadChannelItem(BaseItem item, + private async Task DownloadChannelItem(IChannelMediaItem item, ChannelOptions channelOptions, CancellationToken cancellationToken, string path) { var itemId = item.Id.ToString("N"); - var sources = await _manager.GetChannelItemMediaSources(itemId, false, cancellationToken) + var sources = await _manager.GetStaticMediaSources(item, true, cancellationToken) .ConfigureAwait(false); var cachedVersions = sources.Where(i => i.Protocol == MediaProtocol.File).ToList(); @@ -237,11 +237,9 @@ namespace MediaBrowser.Server.Implementations.Channels } } - var channelItem = (IChannelMediaItem)item; + var destination = Path.Combine(path, item.ChannelId, itemId); - var destination = Path.Combine(path, channelItem.ChannelId, itemId); - - await _manager.DownloadChannelItem(channelItem, destination, new Progress(), cancellationToken) + await _manager.DownloadChannelItem(item, destination, new Progress(), cancellationToken) .ConfigureAwait(false); await RefreshMediaSourceItem(destination, cancellationToken).ConfigureAwait(false); diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs b/MediaBrowser.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs new file mode 100644 index 0000000000..6a7163bb3b --- /dev/null +++ b/MediaBrowser.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs @@ -0,0 +1,43 @@ +using MediaBrowser.Controller.Channels; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Dto; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Channels +{ + public class ChannelDynamicMediaSourceProvider : IMediaSourceProvider + { + private readonly ChannelManager _channelManager; + + public ChannelDynamicMediaSourceProvider(IChannelManager channelManager) + { + _channelManager = (ChannelManager)channelManager; + } + + public Task> GetMediaSources(IHasMediaSources item, CancellationToken cancellationToken) + { + var channelItem = item as IChannelMediaItem; + + if (channelItem != null) + { + return _channelManager.GetDynamicMediaSources(channelItem, cancellationToken); + } + + return Task.FromResult>(new List()); + } + + public Task OpenMediaSource(string openKey, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public Task CloseMediaSource(string closeKey, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + } +} diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs index f0f30229ec..e22bf2e7f7 100644 --- a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs +++ b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs @@ -241,10 +241,25 @@ namespace MediaBrowser.Server.Implementations.Channels return item; } - public async Task> GetChannelItemMediaSources(string id, bool includeDynamicSources, CancellationToken cancellationToken) + public async Task> GetStaticMediaSources(IChannelMediaItem item, bool includeCachedVersions, CancellationToken cancellationToken) { - var item = (IChannelMediaItem)_libraryManager.GetItemById(id); + IEnumerable results = item.ChannelMediaSources; + var sources = SortMediaInfoResults(results) + .Select(i => GetMediaSource(item, i)) + .ToList(); + + if (includeCachedVersions) + { + var cachedVersions = GetCachedChannelItemMediaSources(item); + sources.InsertRange(0, cachedVersions); + } + + return sources.Where(IsValidMediaSource); + } + + public async Task> GetDynamicMediaSources(IChannelMediaItem item, CancellationToken cancellationToken) + { var channel = GetChannel(item.ChannelId); var channelPlugin = GetChannelProvider(channel); @@ -252,24 +267,25 @@ namespace MediaBrowser.Server.Implementations.Channels IEnumerable results; - if (requiresCallback != null && includeDynamicSources) + if (requiresCallback != null) { results = await GetChannelItemMediaSourcesInternal(requiresCallback, item.ExternalId, cancellationToken) - .ConfigureAwait(false); + .ConfigureAwait(false); } else { - results = item.ChannelMediaSources; + results = new List(); } - var sources = SortMediaInfoResults(results).Select(i => GetMediaSource(item, i)) + var list = SortMediaInfoResults(results) + .Select(i => GetMediaSource(item, i)) + .Where(IsValidMediaSource) .ToList(); var cachedVersions = GetCachedChannelItemMediaSources(item); + list.InsertRange(0, cachedVersions); - sources.InsertRange(0, cachedVersions); - - return sources.Where(IsValidMediaSource); + return list; } private readonly ConcurrentDictionary>> _channelItemMediaInfo = @@ -297,14 +313,7 @@ namespace MediaBrowser.Server.Implementations.Channels return list; } - public IEnumerable GetCachedChannelItemMediaSources(string id) - { - var item = (IChannelMediaItem)_libraryManager.GetItemById(id); - - return GetCachedChannelItemMediaSources(item); - } - - public IEnumerable GetCachedChannelItemMediaSources(IChannelMediaItem item) + private IEnumerable GetCachedChannelItemMediaSources(IChannelMediaItem item) { var filenamePrefix = item.Id.ToString("N"); var parentPath = Path.Combine(ChannelDownloadPath, item.ChannelId); @@ -339,7 +348,6 @@ namespace MediaBrowser.Server.Implementations.Channels if (source != null) { - source.Type = MediaSourceType.Cache; return new[] { source }; } } @@ -1408,8 +1416,7 @@ namespace MediaBrowser.Server.Implementations.Channels public async Task DownloadChannelItem(IChannelMediaItem item, string destination, IProgress progress, CancellationToken cancellationToken) { - var itemId = item.Id.ToString("N"); - var sources = await GetChannelItemMediaSources(itemId, true, cancellationToken) + var sources = await GetDynamicMediaSources(item, cancellationToken) .ConfigureAwait(false); var list = sources.Where(i => i.Protocol == MediaProtocol.Http).ToList(); diff --git a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs index 39219b5415..40cf240d7e 100644 --- a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs +++ b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Channels; +using System.Collections.Concurrent; +using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; @@ -13,25 +14,24 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Server.Implementations.LiveTv; namespace MediaBrowser.Server.Implementations.Library { - public class MediaSourceManager : IMediaSourceManager + public class MediaSourceManager : IMediaSourceManager, IDisposable { private readonly IItemRepository _itemRepo; private readonly IUserManager _userManager; private readonly ILibraryManager _libraryManager; - private readonly IChannelManager _channelManager; private IMediaSourceProvider[] _providers; private readonly ILogger _logger; - public MediaSourceManager(IItemRepository itemRepo, IUserManager userManager, ILibraryManager libraryManager, IChannelManager channelManager, ILogger logger) + public MediaSourceManager(IItemRepository itemRepo, IUserManager userManager, ILibraryManager libraryManager, ILogger logger) { _itemRepo = itemRepo; _userManager = userManager; _libraryManager = libraryManager; - _channelManager = channelManager; _logger = logger; } @@ -133,24 +133,15 @@ namespace MediaBrowser.Server.Implementations.Library IEnumerable mediaSources; var hasMediaSources = (IHasMediaSources)item; - var channelItem = item as IChannelMediaItem; - if (channelItem != null) + if (string.IsNullOrWhiteSpace(userId)) { - mediaSources = await _channelManager.GetChannelItemMediaSources(id, true, cancellationToken) - .ConfigureAwait(false); + mediaSources = hasMediaSources.GetMediaSources(enablePathSubstitution); } else { - if (string.IsNullOrWhiteSpace(userId)) - { - mediaSources = hasMediaSources.GetMediaSources(enablePathSubstitution); - } - else - { - var user = _userManager.GetUserById(userId); - mediaSources = GetStaticMediaSources(hasMediaSources, enablePathSubstitution, user); - } + var user = _userManager.GetUserById(userId); + mediaSources = GetStaticMediaSources(hasMediaSources, enablePathSubstitution, user); } var dynamicMediaSources = await GetDynamicMediaSources(hasMediaSources, cancellationToken).ConfigureAwait(false); @@ -161,11 +152,16 @@ namespace MediaBrowser.Server.Implementations.Library foreach (var source in dynamicMediaSources) { - source.SupportsTranscoding = false; - if (source.Protocol == MediaProtocol.File) { source.SupportsDirectStream = File.Exists(source.Path); + + // TODO: Path substitution + } + else if (source.Protocol == MediaProtocol.Http) + { + // TODO: Allow this when the source is plain http, e.g. not HLS or Mpeg Dash + source.SupportsDirectStream = false; } else { @@ -175,7 +171,7 @@ namespace MediaBrowser.Server.Implementations.Library list.Add(source); } - return SortMediaSources(list); + return SortMediaSources(list).Where(i => i.Type != MediaSourceType.Placeholder); } private async Task> GetDynamicMediaSources(IHasMediaSources item, CancellationToken cancellationToken) @@ -190,7 +186,15 @@ namespace MediaBrowser.Server.Implementations.Library { try { - return await provider.GetMediaSources(item, cancellationToken).ConfigureAwait(false); + var sources = await provider.GetMediaSources(item, cancellationToken).ConfigureAwait(false); + var list = sources.ToList(); + + foreach (var mediaSource in list) + { + SetKeyProperties(provider, mediaSource); + } + + return list; } catch (Exception ex) { @@ -199,6 +203,21 @@ namespace MediaBrowser.Server.Implementations.Library } } + private void SetKeyProperties(IMediaSourceProvider provider, MediaSourceInfo mediaSource) + { + var prefix = provider.GetType().FullName.GetMD5().ToString("N") + "|"; + + if (!string.IsNullOrWhiteSpace(mediaSource.OpenKey) && !mediaSource.OpenKey.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) + { + mediaSource.OpenKey = prefix + mediaSource.OpenKey; + } + + if (!string.IsNullOrWhiteSpace(mediaSource.CloseKey) && !mediaSource.CloseKey.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) + { + mediaSource.CloseKey = prefix + mediaSource.CloseKey; + } + } + public Task> GetPlayackMediaSources(string id, bool enablePathSubstitution, CancellationToken cancellationToken) { return GetPlayackMediaSources(id, null, enablePathSubstitution, cancellationToken); @@ -294,5 +313,90 @@ namespace MediaBrowser.Server.Implementations.Library { return GetStaticMediaSources(item, enablePathSubstitution).FirstOrDefault(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase)); } + + private readonly ConcurrentDictionary _openStreams = + new ConcurrentDictionary(); + private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1); + public async Task OpenMediaSource(string openKey, CancellationToken cancellationToken) + { + await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + + try + { + var tuple = GetProvider(openKey); + var provider = tuple.Item1; + + var mediaSource = await provider.OpenMediaSource(tuple.Item2, cancellationToken).ConfigureAwait(false); + + SetKeyProperties(provider, mediaSource); + + _openStreams.AddOrUpdate(mediaSource.CloseKey, mediaSource.CloseKey, (key, i) => mediaSource.CloseKey); + + return mediaSource; + } + finally + { + _liveStreamSemaphore.Release(); + } + } + + public async Task CloseMediaSource(string closeKey, CancellationToken cancellationToken) + { + await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + + try + { + var tuple = GetProvider(closeKey); + + await tuple.Item1.OpenMediaSource(tuple.Item2, cancellationToken).ConfigureAwait(false); + + string removedKey; + _openStreams.TryRemove(closeKey, out removedKey); + } + finally + { + _liveStreamSemaphore.Release(); + } + } + + private Tuple GetProvider(string key) + { + var keys = key.Split(new[] { '|' }, 2); + + var provider = _providers.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N"), keys[0], StringComparison.OrdinalIgnoreCase)); + + return new Tuple(provider, keys[1]); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Dispose(true); + } + + private readonly object _disposeLock = new object(); + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool dispose) + { + if (dispose) + { + lock (_disposeLock) + { + foreach (var key in _openStreams.Keys.ToList()) + { + var task = CloseMediaSource(key, CancellationToken.None); + + Task.WaitAll(task); + } + + _openStreams.Clear(); + } + } + } } } diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index 59daa4921a..202a051e3b 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -1,10 +1,8 @@ -using System.Globalization; -using MediaBrowser.Common; +using MediaBrowser.Common; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Progress; using MediaBrowser.Common.ScheduledTasks; -using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; @@ -15,7 +13,6 @@ using MediaBrowser.Controller.Localization; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Sorting; -using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.LiveTv; @@ -342,6 +339,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv var service = GetService(channel); _logger.Info("Opening channel stream from {0}, external channel Id: {1}", service.Name, channel.ExternalId); info = await service.GetChannelStream(channel.ExternalId, null, cancellationToken).ConfigureAwait(false); + info.RequiresClosing = true; + info.CloseKey = info.Id; } else { @@ -351,6 +350,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv _logger.Info("Opening recording stream from {0}, external recording Id: {1}", service.Name, recording.RecordingInfo.Id); info = await service.GetRecordingStream(recording.RecordingInfo.Id, null, cancellationToken).ConfigureAwait(false); + info.RequiresClosing = true; + info.CloseKey = info.Id; } _logger.Info("Live stream info: {0}", _jsonSerializer.SerializeToString(info)); diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs new file mode 100644 index 0000000000..186bc499db --- /dev/null +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs @@ -0,0 +1,77 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Model.Dto; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.LiveTv +{ + public class LiveTvMediaSourceProvider : IMediaSourceProvider + { + private readonly ILiveTvManager _liveTvManager; + + public LiveTvMediaSourceProvider(ILiveTvManager liveTvManager) + { + _liveTvManager = liveTvManager; + } + + public Task> GetMediaSources(IHasMediaSources item, CancellationToken cancellationToken) + { + var channelItem = item as ILiveTvItem; + + if (channelItem != null) + { + var hasMetadata = (IHasMetadata)channelItem; + + if (string.IsNullOrWhiteSpace(hasMetadata.Path)) + { + return GetMediaSourcesInternal(channelItem, cancellationToken); + } + } + + return Task.FromResult>(new List()); + } + + private async Task> GetMediaSourcesInternal(ILiveTvItem item, CancellationToken cancellationToken) + { + var hasMediaSources = (IHasMediaSources)item; + + var sources = hasMediaSources.GetMediaSources(false) + .ToList(); + + foreach (var source in sources) + { + source.Type = MediaSourceType.Default; + source.RequiresOpening = true; + + var openKeys = new List(); + openKeys.Add(item.GetType().Name); + openKeys.Add(item.Id.ToString("N")); + source.OpenKey = string.Join("|", openKeys.ToArray()); + } + + return sources; + } + + public async Task OpenMediaSource(string openKey, CancellationToken cancellationToken) + { + var keys = openKey.Split(new[] { '|' }, 2); + + if (string.Equals(keys[0], typeof(LiveTvChannel).Name, StringComparison.OrdinalIgnoreCase)) + { + return await _liveTvManager.GetChannelStream(keys[1], cancellationToken).ConfigureAwait(false); + } + + return await _liveTvManager.GetRecordingStream(keys[1], cancellationToken).ConfigureAwait(false); + } + + public Task CloseMediaSource(string closeKey, CancellationToken cancellationToken) + { + return _liveTvManager.CloseLiveStream(closeKey, cancellationToken); + } + } +} diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 3eb414068f..db2397d2f7 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -111,6 +111,7 @@ + @@ -225,6 +226,7 @@ + diff --git a/MediaBrowser.Server.Implementations/Sync/MediaSync.cs b/MediaBrowser.Server.Implementations/Sync/MediaSync.cs index 03a7e92a45..dd8ce82ef1 100644 --- a/MediaBrowser.Server.Implementations/Sync/MediaSync.cs +++ b/MediaBrowser.Server.Implementations/Sync/MediaSync.cs @@ -161,6 +161,7 @@ namespace MediaBrowser.Server.Implementations.Sync { mediaSource.Path = sendFileResult.Path; mediaSource.Protocol = sendFileResult.Protocol; + mediaSource.RequiredHttpHeaders = sendFileResult.RequiredHttpHeaders; mediaSource.SupportsTranscoding = false; } } diff --git a/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs b/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs index 4172cfc2da..25a52fb950 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Sync; @@ -61,7 +62,7 @@ namespace MediaBrowser.Server.Implementations.Sync { foreach (var mediaSource in localItem.Item.MediaSources) { - await TryAddMediaSource(list, localItem, mediaSource, syncProvider, syncTarget, cancellationToken).ConfigureAwait(false); + AddMediaSource(list, localItem, mediaSource, syncProvider, syncTarget); } } } @@ -71,41 +72,70 @@ namespace MediaBrowser.Server.Implementations.Sync return list; } - private async Task TryAddMediaSource(List list, + private void AddMediaSource(List list, LocalItem item, MediaSourceInfo mediaSource, IServerSyncProvider provider, - SyncTarget target, - CancellationToken cancellationToken) + SyncTarget target) { + SetStaticMediaSourceInfo(item, mediaSource); + var requiresDynamicAccess = provider as IHasDynamicAccess; - if (requiresDynamicAccess == null) + if (requiresDynamicAccess != null) { - list.Add(mediaSource); - return; + mediaSource.RequiresOpening = true; + + var keyList = new List(); + keyList.Add(provider.GetType().FullName.GetMD5().ToString("N")); + keyList.Add(target.Id.GetMD5().ToString("N")); + keyList.Add(item.Id); + mediaSource.OpenKey = string.Join("|", keyList.ToArray()); } + } - try - { - var dynamicInfo = await requiresDynamicAccess.GetSyncedFileInfo(item.LocalPath, target, cancellationToken).ConfigureAwait(false); + public async Task OpenMediaSource(string openKey, CancellationToken cancellationToken) + { + var openKeys = openKey.Split(new[] { '|' }, 3); - foreach (var stream in mediaSource.MediaStreams) - { - var dynamicStreamInfo = await requiresDynamicAccess.GetSyncedFileInfo(stream.ExternalId, target, cancellationToken).ConfigureAwait(false); + var provider = _syncManager.ServerSyncProviders + .FirstOrDefault(i => string.Equals(openKeys[0], i.GetType().FullName.GetMD5().ToString("N"), StringComparison.OrdinalIgnoreCase)); - stream.Path = dynamicStreamInfo.Path; - } + var target = provider.GetAllSyncTargets() + .FirstOrDefault(i => string.Equals(openKeys[1], i.Id.GetMD5().ToString("N"), StringComparison.OrdinalIgnoreCase)); - mediaSource.Path = dynamicInfo.Path; - mediaSource.Protocol = dynamicInfo.Protocol; + var dataProvider = _syncManager.GetDataProvider(provider, target); + var localItem = await dataProvider.Get(target, openKeys[2]).ConfigureAwait(false); - list.Add(mediaSource); - } - catch (Exception ex) + var requiresDynamicAccess = (IHasDynamicAccess)provider; + var dynamicInfo = await requiresDynamicAccess.GetSyncedFileInfo(localItem.LocalPath, target, cancellationToken).ConfigureAwait(false); + + var mediaSource = localItem.Item.MediaSources.First(); + SetStaticMediaSourceInfo(localItem, mediaSource); + + foreach (var stream in mediaSource.MediaStreams) { - _logger.ErrorException("Error getting dynamic media source info", ex); + var dynamicStreamInfo = await requiresDynamicAccess.GetSyncedFileInfo(stream.ExternalId, target, cancellationToken).ConfigureAwait(false); + + stream.Path = dynamicStreamInfo.Path; } + + mediaSource.Path = dynamicInfo.Path; + mediaSource.Protocol = dynamicInfo.Protocol; + mediaSource.RequiredHttpHeaders = dynamicInfo.RequiredHttpHeaders; + + return mediaSource; + } + + private void SetStaticMediaSourceInfo(LocalItem item, MediaSourceInfo mediaSource) + { + mediaSource.Id = item.Id; + mediaSource.SupportsTranscoding = false; + } + + public Task CloseMediaSource(string closeKey, CancellationToken cancellationToken) + { + throw new NotImplementedException(); } } } diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index 9a7f03341c..039c5edf38 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -472,7 +472,7 @@ namespace MediaBrowser.Server.Startup.Common ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, LogManager.GetLogger("ChannelManager"), ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, LocalizationManager, HttpClient); RegisterSingleInstance(ChannelManager); - MediaSourceManager = new MediaSourceManager(ItemRepository, UserManager, LibraryManager, ChannelManager, LogManager.GetLogger("MediaSourceManager")); + MediaSourceManager = new MediaSourceManager(ItemRepository, UserManager, LibraryManager, LogManager.GetLogger("MediaSourceManager")); RegisterSingleInstance(MediaSourceManager); SessionManager = new SessionManager(UserDataManager, LogManager.GetLogger("SessionManager"), UserRepository, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, JsonSerializer, this, HttpClient, AuthenticationRepository, DeviceManager, MediaSourceManager); diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index 31441ff791..e1659bfb28 100644 --- a/Nuget/MediaBrowser.Common.Internal.nuspec +++ b/Nuget/MediaBrowser.Common.Internal.nuspec @@ -9,8 +9,8 @@ https://github.com/MediaBrowser/MediaBrowser http://www.mb3admin.com/images/mb3icons1-1.png false - Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption. - Copyright © Media Browser 2013 + Contains common components shared by Emby Theater and Emby Server. Not intended for plugin developer consumption. + Copyright © Emby 2013 diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index 568a47dfe0..294bc519eb 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -4,13 +4,13 @@ MediaBrowser.Common 3.0.603 MediaBrowser.Common - Media Browser Team + Emby Team ebr,Luke,scottisafool https://github.com/MediaBrowser/MediaBrowser http://www.mb3admin.com/images/mb3icons1-1.png false - Contains common model objects and interfaces used by all Media Browser solutions. - Copyright © Media Browser 2013 + Contains common model objects and interfaces used by all Emby solutions. + Copyright © Emby 2013 diff --git a/Nuget/MediaBrowser.Model.Signed.nuspec b/Nuget/MediaBrowser.Model.Signed.nuspec index fb00fd8407..bcbd1d5bea 100644 --- a/Nuget/MediaBrowser.Model.Signed.nuspec +++ b/Nuget/MediaBrowser.Model.Signed.nuspec @@ -4,13 +4,13 @@ MediaBrowser.Model.Signed 3.0.603 MediaBrowser.Model - Signed Edition - Media Browser Team + Emby Team ebr,Luke,scottisafool https://github.com/MediaBrowser/MediaBrowser http://www.mb3admin.com/images/mb3icons1-1.png false - Contains common model objects and interfaces used by all Media Browser solutions. - Copyright © Media Browser 2013 + Contains common model objects and interfaces used by all Emby solutions. + Copyright © Emby 2013 diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index c36fb6d6cd..ee3925db5b 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -4,13 +4,13 @@ MediaBrowser.Server.Core 3.0.603 Media Browser.Server.Core - Media Browser Team + Emby Team ebr,Luke,scottisafool https://github.com/MediaBrowser/MediaBrowser http://www.mb3admin.com/images/mb3icons1-1.png false - Contains core components required to build plugins for Media Browser Server. - Copyright © Media Browser 2013 + Contains core components required to build plugins for Emby Server. + Copyright © Emby 2013 From 578dec0c71361be17eed68f82f20840807a9c9f4 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 29 Mar 2015 00:56:39 -0400 Subject: [PATCH 15/52] update stream generation --- .../Playback/BaseStreamingService.cs | 31 ++-- MediaBrowser.Api/Playback/MediaInfoService.cs | 160 +++++++++++------- MediaBrowser.Api/Playback/StreamRequest.cs | 3 +- MediaBrowser.Api/Playback/StreamState.cs | 4 +- .../Library/IMediaSourceManager.cs | 25 ++- .../Library/IMediaSourceProvider.cs | 8 +- .../LiveTv/ILiveTvManager.cs | 16 ++ MediaBrowser.Model/Dto/MediaSourceInfo.cs | 7 +- .../ChannelDynamicMediaSourceProvider.cs | 4 +- .../Library/MediaSourceManager.cs | 155 +++++++++++++++-- .../LiveTv/LiveTvManager.cs | 27 ++- .../LiveTv/LiveTvMediaSourceProvider.cs | 51 ++++-- .../Sync/SyncedMediaSourceProvider.cs | 8 +- 13 files changed, 377 insertions(+), 122 deletions(-) diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 435bda2c44..2e7c9a5a7b 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -5,7 +5,6 @@ using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dlna; @@ -937,7 +936,7 @@ namespace MediaBrowser.Api.Playback if (state.MediaSource.RequiresOpening) { - var mediaSource = await MediaSourceManager.OpenMediaSource(state.MediaSource.OpenKey, cancellationTokenSource.Token) + var mediaSource = await MediaSourceManager.OpenLiveStream(state.MediaSource.OpenToken, false, cancellationTokenSource.Token) .ConfigureAwait(false); AttachMediaSourceInfo(state, mediaSource, state.VideoRequest, state.RequestedUrl); @@ -946,9 +945,11 @@ namespace MediaBrowser.Api.Playback { TryStreamCopy(state, state.VideoRequest); } + } - // TODO: This is only needed for live tv - await Task.Delay(1500, cancellationTokenSource.Token).ConfigureAwait(false); + if (state.MediaSource.BufferMs.HasValue) + { + await Task.Delay(state.MediaSource.BufferMs.Value, cancellationTokenSource.Token).ConfigureAwait(false); } } @@ -1616,12 +1617,20 @@ namespace MediaBrowser.Api.Playback var archivable = item as IArchivable; state.IsInputArchive = archivable != null && archivable.IsArchive; - var mediaSources = await MediaSourceManager.GetPlayackMediaSources(request.Id, false, cancellationToken).ConfigureAwait(false); + MediaSourceInfo mediaSource = null; + if (string.IsNullOrWhiteSpace(request.LiveStreamId)) + { + var mediaSources = await MediaSourceManager.GetPlayackMediaSources(request.Id, false, cancellationToken).ConfigureAwait(false); + + mediaSource = string.IsNullOrEmpty(request.MediaSourceId) + ? mediaSources.First() + : mediaSources.First(i => string.Equals(i.Id, request.MediaSourceId)); + } + else + { + mediaSource = await MediaSourceManager.GetLiveStream(request.LiveStreamId, cancellationToken).ConfigureAwait(false); + } - var mediaSource = string.IsNullOrEmpty(request.MediaSourceId) - ? mediaSources.First() - : mediaSources.First(i => string.Equals(i.Id, request.MediaSourceId)); - var videoRequest = request as VideoStreamRequest; AttachMediaSourceInfo(state, mediaSource, videoRequest, url); @@ -1699,7 +1708,7 @@ namespace MediaBrowser.Api.Playback state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate; state.RunTimeTicks = mediaSource.RunTimeTicks; state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders; - + if (mediaSource.VideoType.HasValue) { state.VideoType = mediaSource.VideoType.Value; @@ -1713,7 +1722,7 @@ namespace MediaBrowser.Api.Playback { state.InputTimestamp = mediaSource.Timestamp.Value; } - + state.InputProtocol = mediaSource.Protocol; state.MediaPath = mediaSource.Path; state.RunTimeTicks = mediaSource.RunTimeTicks; diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs index cef8a34e57..d954c5b197 100644 --- a/MediaBrowser.Api/Playback/MediaInfoService.cs +++ b/MediaBrowser.Api/Playback/MediaInfoService.cs @@ -45,6 +45,9 @@ namespace MediaBrowser.Api.Playback [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] public string UserId { get; set; } + [ApiMember(Name = "MaxStreamingBitrate", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")] + public int? MaxStreamingBitrate { get; set; } + [ApiMember(Name = "StartTimeTicks", Description = "Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")] public long? StartTimeTicks { get; set; } @@ -58,6 +61,20 @@ namespace MediaBrowser.Api.Playback public string MediaSourceId { get; set; } } + [Route("/MediaSources/Open", "POST", Summary = "Opens a media source")] + public class OpenMediaSource : IReturn + { + [ApiMember(Name = "OpenToken", Description = "OpenToken", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string OpenToken { get; set; } + } + + [Route("/MediaSources/Close", "POST", Summary = "Closes a media source")] + public class CloseMediaSource : IReturnVoid + { + [ApiMember(Name = "LiveStreamId", Description = "LiveStreamId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string LiveStreamId { get; set; } + } + [Authenticated] public class MediaInfoService : BaseApiService { @@ -84,6 +101,18 @@ namespace MediaBrowser.Api.Playback return ToOptimizedResult(result); } + public async Task Post(OpenMediaSource request) + { + var result = await _mediaSourceManager.OpenLiveStream(request.OpenToken, false, CancellationToken.None).ConfigureAwait(false); + return ToOptimizedResult(result); + } + + public void Post(CloseMediaSource request) + { + var task = _mediaSourceManager.CloseLiveStream(request.LiveStreamId, CancellationToken.None); + Task.WaitAll(task); + } + public async Task Post(GetPostedPlaybackInfo request) { var info = await GetPlaybackInfo(request.Id, request.UserId, request.MediaSourceId).ConfigureAwait(false); @@ -102,7 +131,7 @@ namespace MediaBrowser.Api.Playback if (profile != null) { var mediaSourceId = request.MediaSourceId; - SetDeviceSpecificData(request.Id, info, profile, authInfo, null, request.StartTimeTicks ?? 0, mediaSourceId, request.AudioStreamIndex, request.SubtitleStreamIndex); + SetDeviceSpecificData(request.Id, info, profile, authInfo, request.MaxStreamingBitrate, request.StartTimeTicks ?? 0, mediaSourceId, request.AudioStreamIndex, request.SubtitleStreamIndex); } return ToOptimizedResult(info); @@ -158,81 +187,94 @@ namespace MediaBrowser.Api.Playback int? audioStreamIndex, int? subtitleStreamIndex) { - var streamBuilder = new StreamBuilder(); - var item = _libraryManager.GetItemById(itemId); foreach (var mediaSource in result.MediaSources) { - var options = new VideoOptions - { - MediaSources = new List { mediaSource }, - Context = EncodingContext.Streaming, - DeviceId = auth.DeviceId, - ItemId = item.Id.ToString("N"), - Profile = profile, - MaxBitrate = maxBitrate - }; - - if (string.Equals(mediaSourceId, mediaSource.Id, StringComparison.OrdinalIgnoreCase)) - { - options.MediaSourceId = mediaSourceId; - options.AudioStreamIndex = audioStreamIndex; - options.SubtitleStreamIndex = subtitleStreamIndex; - } + SetDeviceSpecificData(item, mediaSource, profile, auth, maxBitrate, startTimeTicks, mediaSourceId, audioStreamIndex, subtitleStreamIndex); + } - if (mediaSource.SupportsDirectPlay) - { - var supportsDirectStream = mediaSource.SupportsDirectStream; + SortMediaSources(result); + } - // Dummy this up to fool StreamBuilder - mediaSource.SupportsDirectStream = true; + private void SetDeviceSpecificData(BaseItem item, + MediaSourceInfo mediaSource, + DeviceProfile profile, + AuthorizationInfo auth, + int? maxBitrate, + long startTimeTicks, + string mediaSourceId, + int? audioStreamIndex, + int? subtitleStreamIndex) + { + var streamBuilder = new StreamBuilder(); - // The MediaSource supports direct stream, now test to see if the client supports it - var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ? - streamBuilder.BuildAudioItem(options) : - streamBuilder.BuildVideoItem(options); + var options = new VideoOptions + { + MediaSources = new List { mediaSource }, + Context = EncodingContext.Streaming, + DeviceId = auth.DeviceId, + ItemId = item.Id.ToString("N"), + Profile = profile, + MaxBitrate = maxBitrate + }; + + if (string.Equals(mediaSourceId, mediaSource.Id, StringComparison.OrdinalIgnoreCase)) + { + options.MediaSourceId = mediaSourceId; + options.AudioStreamIndex = audioStreamIndex; + options.SubtitleStreamIndex = subtitleStreamIndex; + } - if (streamInfo == null || !streamInfo.IsDirectStream) - { - mediaSource.SupportsDirectPlay = false; - } + if (mediaSource.SupportsDirectPlay) + { + var supportsDirectStream = mediaSource.SupportsDirectStream; - // Set this back to what it was - mediaSource.SupportsDirectStream = supportsDirectStream; - } + // Dummy this up to fool StreamBuilder + mediaSource.SupportsDirectStream = true; - if (mediaSource.SupportsDirectStream) + // The MediaSource supports direct stream, now test to see if the client supports it + var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ? + streamBuilder.BuildAudioItem(options) : + streamBuilder.BuildVideoItem(options); + + if (streamInfo == null || !streamInfo.IsDirectStream) { - // The MediaSource supports direct stream, now test to see if the client supports it - var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ? - streamBuilder.BuildAudioItem(options) : - streamBuilder.BuildVideoItem(options); - - if (streamInfo == null || !streamInfo.IsDirectStream) - { - mediaSource.SupportsDirectStream = false; - } + mediaSource.SupportsDirectPlay = false; } - if (mediaSource.SupportsTranscoding) + // Set this back to what it was + mediaSource.SupportsDirectStream = supportsDirectStream; + } + + if (mediaSource.SupportsDirectStream) + { + // The MediaSource supports direct stream, now test to see if the client supports it + var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ? + streamBuilder.BuildAudioItem(options) : + streamBuilder.BuildVideoItem(options); + + if (streamInfo == null || !streamInfo.IsDirectStream) { - // The MediaSource supports direct stream, now test to see if the client supports it - var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ? - streamBuilder.BuildAudioItem(options) : - streamBuilder.BuildVideoItem(options); - - if (streamInfo != null && streamInfo.PlayMethod == PlayMethod.Transcode) - { - streamInfo.StartPositionTicks = startTimeTicks; - mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).Substring(1); - mediaSource.TranscodingContainer = streamInfo.Container; - mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol; - } + mediaSource.SupportsDirectStream = false; } } - SortMediaSources(result); + if (mediaSource.SupportsTranscoding) + { + // The MediaSource supports direct stream, now test to see if the client supports it + var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ? + streamBuilder.BuildAudioItem(options) : + streamBuilder.BuildVideoItem(options); + + if (streamInfo != null && streamInfo.PlayMethod == PlayMethod.Transcode) + { + streamInfo.StartPositionTicks = startTimeTicks; + mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).Substring(1); + mediaSource.TranscodingContainer = streamInfo.Container; + mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol; + } + } } private void SortMediaSources(PlaybackInfoResponse result) diff --git a/MediaBrowser.Api/Playback/StreamRequest.cs b/MediaBrowser.Api/Playback/StreamRequest.cs index b52260b506..7ed4fcd965 100644 --- a/MediaBrowser.Api/Playback/StreamRequest.cs +++ b/MediaBrowser.Api/Playback/StreamRequest.cs @@ -72,8 +72,7 @@ namespace MediaBrowser.Api.Playback public string Params { get; set; } public string ClientTime { get; set; } public string StreamId { get; set; } - - public string TranscodingJobId { get; set; } + public string LiveStreamId { get; set; } } public class VideoStreamRequest : StreamRequest diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs index 37f2c7702c..b097f3b6af 100644 --- a/MediaBrowser.Api/Playback/StreamState.cs +++ b/MediaBrowser.Api/Playback/StreamState.cs @@ -182,11 +182,11 @@ namespace MediaBrowser.Api.Playback private async void DisposeLiveStream() { - if (MediaSource.RequiresClosing) + if (MediaSource.RequiresClosing && string.IsNullOrWhiteSpace(Request.LiveStreamId)) { try { - await _mediaSourceManager.CloseMediaSource(MediaSource.CloseKey, CancellationToken.None).ConfigureAwait(false); + await _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId, CancellationToken.None).ConfigureAwait(false); } catch (Exception ex) { diff --git a/MediaBrowser.Controller/Library/IMediaSourceManager.cs b/MediaBrowser.Controller/Library/IMediaSourceManager.cs index fda17aa279..292205c033 100644 --- a/MediaBrowser.Controller/Library/IMediaSourceManager.cs +++ b/MediaBrowser.Controller/Library/IMediaSourceManager.cs @@ -84,17 +84,34 @@ namespace MediaBrowser.Controller.Library /// /// Opens the media source. /// - /// The open key. + /// The open token. + /// if set to true [enable automatic close]. /// The cancellation token. /// Task<MediaSourceInfo>. - Task OpenMediaSource(string openKey, CancellationToken cancellationToken); + Task OpenLiveStream(string openToken, bool enableAutoClose, CancellationToken cancellationToken); + + /// + /// Gets the live stream. + /// + /// The identifier. + /// The cancellation token. + /// Task<MediaSourceInfo>. + Task GetLiveStream(string id, CancellationToken cancellationToken); + + /// + /// Pings the media source. + /// + /// The live stream identifier. + /// The cancellation token. + /// Task. + Task PingLiveStream(string id, CancellationToken cancellationToken); /// /// Closes the media source. /// - /// The close key. + /// The live stream identifier. /// The cancellation token. /// Task. - Task CloseMediaSource(string closeKey, CancellationToken cancellationToken); + Task CloseLiveStream(string id, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Controller/Library/IMediaSourceProvider.cs b/MediaBrowser.Controller/Library/IMediaSourceProvider.cs index c5f5b54010..5b033af4af 100644 --- a/MediaBrowser.Controller/Library/IMediaSourceProvider.cs +++ b/MediaBrowser.Controller/Library/IMediaSourceProvider.cs @@ -19,17 +19,17 @@ namespace MediaBrowser.Controller.Library /// /// Opens the media source. /// - /// The open key. + /// The open token. /// The cancellation token. /// Task<MediaSourceInfo>. - Task OpenMediaSource(string openKey, CancellationToken cancellationToken); + Task OpenMediaSource(string openToken, CancellationToken cancellationToken); /// /// Closes the media source. /// - /// The close key. + /// The live stream identifier. /// The cancellation token. /// Task. - Task CloseMediaSource(string closeKey, CancellationToken cancellationToken); + Task CloseMediaSource(string liveStreamId, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs index 0b58a92328..d5b5d92a6e 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs @@ -301,5 +301,21 @@ namespace MediaBrowser.Controller.LiveTv /// The cancellation token. /// Task<QueryResult<BaseItem>>. Task> GetInternalRecordings(RecordingQuery query, CancellationToken cancellationToken); + + /// + /// Gets the recording media sources. + /// + /// The identifier. + /// The cancellation token. + /// Task<IEnumerable<MediaSourceInfo>>. + Task> GetRecordingMediaSources(string id, CancellationToken cancellationToken); + + /// + /// Gets the channel media sources. + /// + /// The identifier. + /// The cancellation token. + /// Task<IEnumerable<MediaSourceInfo>>. + Task> GetChannelMediaSources(string id, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Model/Dto/MediaSourceInfo.cs b/MediaBrowser.Model/Dto/MediaSourceInfo.cs index 92af8d671c..3b45137241 100644 --- a/MediaBrowser.Model/Dto/MediaSourceInfo.cs +++ b/MediaBrowser.Model/Dto/MediaSourceInfo.cs @@ -27,10 +27,11 @@ namespace MediaBrowser.Model.Dto public bool SupportsDirectPlay { get; set; } public bool RequiresOpening { get; set; } - public string OpenKey { get; set; } + public string OpenToken { get; set; } public bool RequiresClosing { get; set; } - public string CloseKey { get; set; } - + public string LiveStreamId { get; set; } + public int? BufferMs { get; set; } + public VideoType? VideoType { get; set; } public IsoType? IsoType { get; set; } diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs b/MediaBrowser.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs index 6a7163bb3b..dac3a80f2c 100644 --- a/MediaBrowser.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs +++ b/MediaBrowser.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs @@ -30,12 +30,12 @@ namespace MediaBrowser.Server.Implementations.Channels return Task.FromResult>(new List()); } - public Task OpenMediaSource(string openKey, CancellationToken cancellationToken) + public Task OpenMediaSource(string openToken, CancellationToken cancellationToken) { throw new NotImplementedException(); } - public Task CloseMediaSource(string closeKey, CancellationToken cancellationToken) + public Task CloseMediaSource(string liveStreamId, CancellationToken cancellationToken) { throw new NotImplementedException(); } diff --git a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs index 40cf240d7e..3dbcf4aad4 100644 --- a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs +++ b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs @@ -207,14 +207,14 @@ namespace MediaBrowser.Server.Implementations.Library { var prefix = provider.GetType().FullName.GetMD5().ToString("N") + "|"; - if (!string.IsNullOrWhiteSpace(mediaSource.OpenKey) && !mediaSource.OpenKey.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) + if (!string.IsNullOrWhiteSpace(mediaSource.OpenToken) && !mediaSource.OpenToken.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) { - mediaSource.OpenKey = prefix + mediaSource.OpenKey; + mediaSource.OpenToken = prefix + mediaSource.OpenToken; } - if (!string.IsNullOrWhiteSpace(mediaSource.CloseKey) && !mediaSource.CloseKey.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) + if (!string.IsNullOrWhiteSpace(mediaSource.LiveStreamId) && !mediaSource.LiveStreamId.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) { - mediaSource.CloseKey = prefix + mediaSource.CloseKey; + mediaSource.LiveStreamId = prefix + mediaSource.LiveStreamId; } } @@ -314,24 +314,41 @@ namespace MediaBrowser.Server.Implementations.Library return GetStaticMediaSources(item, enablePathSubstitution).FirstOrDefault(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase)); } - private readonly ConcurrentDictionary _openStreams = - new ConcurrentDictionary(); + private readonly ConcurrentDictionary _openStreams = new ConcurrentDictionary(); private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1); - public async Task OpenMediaSource(string openKey, CancellationToken cancellationToken) + + public async Task OpenLiveStream(string openToken, bool enableAutoClose, CancellationToken cancellationToken) { await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); try { - var tuple = GetProvider(openKey); + var tuple = GetProvider(openToken); var provider = tuple.Item1; var mediaSource = await provider.OpenMediaSource(tuple.Item2, cancellationToken).ConfigureAwait(false); SetKeyProperties(provider, mediaSource); - _openStreams.AddOrUpdate(mediaSource.CloseKey, mediaSource.CloseKey, (key, i) => mediaSource.CloseKey); - + var info = new LiveStreamInfo + { + Date = DateTime.UtcNow, + EnableCloseTimer = enableAutoClose, + Id = mediaSource.LiveStreamId, + MediaSource = mediaSource + }; + _openStreams.AddOrUpdate(mediaSource.LiveStreamId, info, (key, i) => info); + + if (enableAutoClose) + { + StartCloseTimer(); + } + + if (!string.IsNullOrWhiteSpace(mediaSource.TranscodingUrl)) + { + mediaSource.TranscodingUrl += "&LiveStreamId=" + mediaSource.LiveStreamId; + } + return mediaSource; } finally @@ -340,18 +357,70 @@ namespace MediaBrowser.Server.Implementations.Library } } - public async Task CloseMediaSource(string closeKey, CancellationToken cancellationToken) + public async Task GetLiveStream(string id, CancellationToken cancellationToken) + { + await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + + try + { + LiveStreamInfo info; + if (_openStreams.TryGetValue(id, out info)) + { + return info.MediaSource; + } + else + { + throw new ResourceNotFoundException(); + } + } + finally + { + _liveStreamSemaphore.Release(); + } + } + + public async Task PingLiveStream(string id, CancellationToken cancellationToken) + { + await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + + try + { + LiveStreamInfo info; + if (_openStreams.TryGetValue(id, out info)) + { + info.Date = DateTime.UtcNow; + } + else + { + _logger.Error("Failed to update MediaSource timestamp for {0}", id); + } + } + finally + { + _liveStreamSemaphore.Release(); + } + } + + public async Task CloseLiveStream(string id, CancellationToken cancellationToken) { await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); try { - var tuple = GetProvider(closeKey); + var tuple = GetProvider(id); - await tuple.Item1.OpenMediaSource(tuple.Item2, cancellationToken).ConfigureAwait(false); + await tuple.Item1.CloseMediaSource(tuple.Item2, cancellationToken).ConfigureAwait(false); - string removedKey; - _openStreams.TryRemove(closeKey, out removedKey); + LiveStreamInfo removed; + if (_openStreams.TryRemove(id, out removed)) + { + removed.Closed = true; + } + + if (_openStreams.Count == 0) + { + StopCloseTimer(); + } } finally { @@ -368,11 +437,56 @@ namespace MediaBrowser.Server.Implementations.Library return new Tuple(provider, keys[1]); } + private Timer _closeTimer; + private readonly TimeSpan _openStreamMaxAge = TimeSpan.FromSeconds(40); + + private void StartCloseTimer() + { + StopCloseTimer(); + + _closeTimer = new Timer(CloseTimerCallback, null, _openStreamMaxAge, _openStreamMaxAge); + } + + private void StopCloseTimer() + { + var timer = _closeTimer; + + if (timer != null) + { + _closeTimer = null; + timer.Dispose(); + } + } + + private async void CloseTimerCallback(object state) + { + var infos = _openStreams + .Values + .Where(i => i.EnableCloseTimer && (DateTime.UtcNow - i.Date) > _openStreamMaxAge) + .ToList(); + + foreach (var info in infos) + { + if (!info.Closed) + { + try + { + await CloseLiveStream(info.Id, CancellationToken.None).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.ErrorException("Error closing media source", ex); + } + } + } + } + /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { + StopCloseTimer(); Dispose(true); } @@ -389,7 +503,7 @@ namespace MediaBrowser.Server.Implementations.Library { foreach (var key in _openStreams.Keys.ToList()) { - var task = CloseMediaSource(key, CancellationToken.None); + var task = CloseLiveStream(key, CancellationToken.None); Task.WaitAll(task); } @@ -398,5 +512,14 @@ namespace MediaBrowser.Server.Implementations.Library } } } + + private class LiveStreamInfo + { + public DateTime Date; + public bool EnableCloseTimer; + public string Id; + public bool Closed; + public MediaSourceInfo MediaSource; + } } } diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index 202a051e3b..86b31e0e5d 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -313,6 +313,22 @@ namespace MediaBrowser.Server.Implementations.LiveTv return await GetLiveStream(id, true, cancellationToken).ConfigureAwait(false); } + public async Task> GetRecordingMediaSources(string id, CancellationToken cancellationToken) + { + var item = await GetInternalRecording(id, cancellationToken).ConfigureAwait(false); + var service = GetService(item); + + return await service.GetRecordingStreamMediaSources(id, cancellationToken).ConfigureAwait(false); + } + + public async Task> GetChannelMediaSources(string id, CancellationToken cancellationToken) + { + var item = GetInternalChannel(id); + var service = GetService(item); + + return await service.GetChannelStreamMediaSources(id, cancellationToken).ConfigureAwait(false); + } + private ILiveTvService GetService(ILiveTvItem item) { return GetService(item.ServiceName); @@ -330,7 +346,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv try { MediaSourceInfo info; - var isVideo = true; + bool isVideo; if (isChannel) { @@ -340,7 +356,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv _logger.Info("Opening channel stream from {0}, external channel Id: {1}", service.Name, channel.ExternalId); info = await service.GetChannelStream(channel.ExternalId, null, cancellationToken).ConfigureAwait(false); info.RequiresClosing = true; - info.CloseKey = info.Id; + info.LiveStreamId = info.Id; } else { @@ -351,7 +367,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv _logger.Info("Opening recording stream from {0}, external recording Id: {1}", service.Name, recording.RecordingInfo.Id); info = await service.GetRecordingStream(recording.RecordingInfo.Id, null, cancellationToken).ConfigureAwait(false); info.RequiresClosing = true; - info.CloseKey = info.Id; + info.LiveStreamId = info.Id; } _logger.Info("Live stream info: {0}", _jsonSerializer.SerializeToString(info)); @@ -393,7 +409,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv { Type = MediaStreamType.Video, // Set the index to -1 because we don't know the exact index of the video stream within the container - Index = -1 + Index = -1, + + // Set to true if unknown to enable deinterlacing + IsInterlaced = true }, new MediaStream { diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs index 186bc499db..5de4cf4998 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs @@ -2,6 +2,8 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Serialization; using System; using System.Collections.Generic; using System.Linq; @@ -13,10 +15,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv public class LiveTvMediaSourceProvider : IMediaSourceProvider { private readonly ILiveTvManager _liveTvManager; + private readonly IJsonSerializer _jsonSerializer; + private readonly ILogger _logger; - public LiveTvMediaSourceProvider(ILiveTvManager liveTvManager) + public LiveTvMediaSourceProvider(ILiveTvManager liveTvManager, IJsonSerializer jsonSerializer, ILogManager logManager) { _liveTvManager = liveTvManager; + _jsonSerializer = jsonSerializer; + _logger = logManager.GetLogger(GetType().Name); } public Task> GetMediaSources(IHasMediaSources item, CancellationToken cancellationToken) @@ -38,28 +44,51 @@ namespace MediaBrowser.Server.Implementations.LiveTv private async Task> GetMediaSourcesInternal(ILiveTvItem item, CancellationToken cancellationToken) { - var hasMediaSources = (IHasMediaSources)item; + IEnumerable sources; - var sources = hasMediaSources.GetMediaSources(false) - .ToList(); + try + { + if (item is ILiveTvRecording) + { + sources = await _liveTvManager.GetRecordingMediaSources(item.Id.ToString("N"), cancellationToken) + .ConfigureAwait(false); + } + else + { + sources = await _liveTvManager.GetChannelMediaSources(item.Id.ToString("N"), cancellationToken) + .ConfigureAwait(false); + } + } + catch (NotImplementedException) + { + var hasMediaSources = (IHasMediaSources)item; + + sources = hasMediaSources.GetMediaSources(false) + .ToList(); + } - foreach (var source in sources) + var list = sources.ToList(); + + foreach (var source in list) { source.Type = MediaSourceType.Default; source.RequiresOpening = true; + source.BufferMs = source.BufferMs ?? 1500; var openKeys = new List(); openKeys.Add(item.GetType().Name); openKeys.Add(item.Id.ToString("N")); - source.OpenKey = string.Join("|", openKeys.ToArray()); + source.OpenToken = string.Join("|", openKeys.ToArray()); } - return sources; + _logger.Debug("MediaSources: {0}", _jsonSerializer.SerializeToString(list)); + + return list; } - public async Task OpenMediaSource(string openKey, CancellationToken cancellationToken) + public async Task OpenMediaSource(string openToken, CancellationToken cancellationToken) { - var keys = openKey.Split(new[] { '|' }, 2); + var keys = openToken.Split(new[] { '|' }, 2); if (string.Equals(keys[0], typeof(LiveTvChannel).Name, StringComparison.OrdinalIgnoreCase)) { @@ -69,9 +98,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv return await _liveTvManager.GetRecordingStream(keys[1], cancellationToken).ConfigureAwait(false); } - public Task CloseMediaSource(string closeKey, CancellationToken cancellationToken) + public Task CloseMediaSource(string liveStreamId, CancellationToken cancellationToken) { - return _liveTvManager.CloseLiveStream(closeKey, cancellationToken); + return _liveTvManager.CloseLiveStream(liveStreamId, cancellationToken); } } } diff --git a/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs b/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs index 25a52fb950..1c17b99936 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs @@ -90,13 +90,13 @@ namespace MediaBrowser.Server.Implementations.Sync keyList.Add(provider.GetType().FullName.GetMD5().ToString("N")); keyList.Add(target.Id.GetMD5().ToString("N")); keyList.Add(item.Id); - mediaSource.OpenKey = string.Join("|", keyList.ToArray()); + mediaSource.OpenToken = string.Join("|", keyList.ToArray()); } } - public async Task OpenMediaSource(string openKey, CancellationToken cancellationToken) + public async Task OpenMediaSource(string openToken, CancellationToken cancellationToken) { - var openKeys = openKey.Split(new[] { '|' }, 3); + var openKeys = openToken.Split(new[] { '|' }, 3); var provider = _syncManager.ServerSyncProviders .FirstOrDefault(i => string.Equals(openKeys[0], i.GetType().FullName.GetMD5().ToString("N"), StringComparison.OrdinalIgnoreCase)); @@ -133,7 +133,7 @@ namespace MediaBrowser.Server.Implementations.Sync mediaSource.SupportsTranscoding = false; } - public Task CloseMediaSource(string closeKey, CancellationToken cancellationToken) + public Task CloseMediaSource(string liveStreamId, CancellationToken cancellationToken) { throw new NotImplementedException(); } From 87bf3bbb8fca91d254b5e9eab4a143cf96af4cac Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 29 Mar 2015 11:04:33 -0400 Subject: [PATCH 16/52] add device null check --- .../Session/SessionManager.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index 201e86fe2f..ec93ae8766 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -432,9 +432,12 @@ namespace MediaBrowser.Server.Implementations.Session device = device ?? _deviceManager.GetDevice(deviceId); - if (!string.IsNullOrEmpty(device.CustomName)) + if (device != null) { - deviceName = device.CustomName; + if (!string.IsNullOrEmpty(device.CustomName)) + { + deviceName = device.CustomName; + } } sessionInfo.DeviceName = deviceName; From a79962b7ebfe59d837970581ac1b5f184b0aa42d Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 29 Mar 2015 12:45:16 -0400 Subject: [PATCH 17/52] update live stream generation --- .../Playback/BaseStreamingService.cs | 9 ++- .../Playback/Hls/DynamicHlsService.cs | 4 +- MediaBrowser.Api/Playback/MediaInfoService.cs | 45 +++++++++++---- .../Library/IMediaSourceManager.cs | 5 +- .../MediaBrowser.Model.Portable.csproj | 6 ++ .../MediaBrowser.Model.net35.csproj | 6 ++ .../ApiClient/IConnectionManager.cs | 6 ++ MediaBrowser.Model/MediaBrowser.Model.csproj | 2 + .../MediaInfo/LiveStreamRequest.cs | 16 ++++++ .../MediaInfo/LiveStreamResponse.cs | 9 +++ .../Library/MediaSourceManager.cs | 27 ++++++--- .../Session/SessionManager.cs | 57 ++++++++++++------- .../ApplicationHost.cs | 2 +- 13 files changed, 145 insertions(+), 49 deletions(-) create mode 100644 MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs create mode 100644 MediaBrowser.Model/MediaInfo/LiveStreamResponse.cs diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 2e7c9a5a7b..5a4cdaaa93 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -936,10 +936,13 @@ namespace MediaBrowser.Api.Playback if (state.MediaSource.RequiresOpening) { - var mediaSource = await MediaSourceManager.OpenLiveStream(state.MediaSource.OpenToken, false, cancellationTokenSource.Token) - .ConfigureAwait(false); + var liveStreamResponse = await MediaSourceManager.OpenLiveStream(new LiveStreamRequest + { + OpenToken = state.MediaSource.OpenToken + + }, false, cancellationTokenSource.Token).ConfigureAwait(false); - AttachMediaSourceInfo(state, mediaSource, state.VideoRequest, state.RequestedUrl); + AttachMediaSourceInfo(state, liveStreamResponse.MediaSource, state.VideoRequest, state.RequestedUrl); if (state.VideoRequest != null) { diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index b166bc319f..4f7c0444b2 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -698,7 +698,7 @@ namespace MediaBrowser.Api.Playback.Hls { var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d.ts"; - return string.Format("{0} {1} -map_metadata -1 -threads {2} {3} {4} -copyts -flags -global_header {5} -f segment -segment_time {6} -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"", + return string.Format("{0} {1} -map_metadata -1 -threads {2} {3} {4} -copyts -flags -global_header -sc_threshold 0 {5} -f segment -segment_time {6} -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"", inputModifier, GetInputArgument(state), threads, @@ -712,7 +712,7 @@ namespace MediaBrowser.Api.Playback.Hls ).Trim(); } - return string.Format("{0} {1} -map_metadata -1 -threads {2} {3} {4} -copyts -flags -global_header {5} -hls_time {6} -start_number {7} -hls_list_size {8} -y \"{9}\"", + return string.Format("{0} {1} -map_metadata -1 -threads {2} {3} {4} -copyts -flags -global_header -sc_threshold 0 {5} -hls_time {6} -start_number {7} -hls_list_size {8} -y \"{9}\"", inputModifier, GetInputArgument(state), threads, diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs index d954c5b197..6f8c2cc505 100644 --- a/MediaBrowser.Api/Playback/MediaInfoService.cs +++ b/MediaBrowser.Api/Playback/MediaInfoService.cs @@ -61,14 +61,12 @@ namespace MediaBrowser.Api.Playback public string MediaSourceId { get; set; } } - [Route("/MediaSources/Open", "POST", Summary = "Opens a media source")] - public class OpenMediaSource : IReturn + [Route("/LiveStreams/Open", "POST", Summary = "Opens a media source")] + public class OpenMediaSource : LiveStreamRequest, IReturn { - [ApiMember(Name = "OpenToken", Description = "OpenToken", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string OpenToken { get; set; } } - [Route("/MediaSources/Close", "POST", Summary = "Closes a media source")] + [Route("/LiveStreams/Close", "POST", Summary = "Closes a media source")] public class CloseMediaSource : IReturnVoid { [ApiMember(Name = "LiveStreamId", Description = "LiveStreamId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] @@ -103,7 +101,32 @@ namespace MediaBrowser.Api.Playback public async Task Post(OpenMediaSource request) { - var result = await _mediaSourceManager.OpenLiveStream(request.OpenToken, false, CancellationToken.None).ConfigureAwait(false); + var authInfo = AuthorizationContext.GetAuthorizationInfo(Request); + + var result = await _mediaSourceManager.OpenLiveStream(request, false, CancellationToken.None).ConfigureAwait(false); + + var profile = request.DeviceProfile; + if (profile == null) + { + var caps = _deviceManager.GetCapabilities(authInfo.DeviceId); + if (caps != null) + { + profile = caps.DeviceProfile; + } + } + + if (profile != null) + { + var item = _libraryManager.GetItemById(request.ItemId); + + SetDeviceSpecificData(item, result.MediaSource, profile, authInfo, request.MaxStreamingBitrate, request.StartTimeTicks ?? 0, result.MediaSource.Id, request.AudioStreamIndex, request.SubtitleStreamIndex); + } + + if (!string.IsNullOrWhiteSpace(result.MediaSource.TranscodingUrl)) + { + result.MediaSource.TranscodingUrl += "&LiveStreamId=" + result.MediaSource.LiveStreamId; + } + return ToOptimizedResult(result); } @@ -177,11 +200,11 @@ namespace MediaBrowser.Api.Playback return result; } - private void SetDeviceSpecificData(string itemId, - PlaybackInfoResponse result, - DeviceProfile profile, - AuthorizationInfo auth, - int? maxBitrate, + private void SetDeviceSpecificData(string itemId, + PlaybackInfoResponse result, + DeviceProfile profile, + AuthorizationInfo auth, + int? maxBitrate, long startTimeTicks, string mediaSourceId, int? audioStreamIndex, diff --git a/MediaBrowser.Controller/Library/IMediaSourceManager.cs b/MediaBrowser.Controller/Library/IMediaSourceManager.cs index 292205c033..9cbbabc8db 100644 --- a/MediaBrowser.Controller/Library/IMediaSourceManager.cs +++ b/MediaBrowser.Controller/Library/IMediaSourceManager.cs @@ -2,6 +2,7 @@ using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.MediaInfo; using System; using System.Collections.Generic; using System.Threading; @@ -84,11 +85,11 @@ namespace MediaBrowser.Controller.Library /// /// Opens the media source. /// - /// The open token. + /// The request. /// if set to true [enable automatic close]. /// The cancellation token. /// Task<MediaSourceInfo>. - Task OpenLiveStream(string openToken, bool enableAutoClose, CancellationToken cancellationToken); + Task OpenLiveStream(LiveStreamRequest request, bool enableAutoClose, CancellationToken cancellationToken); /// /// Gets the live stream. diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj index ab77dc271b..fdedc51a21 100644 --- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj +++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj @@ -803,6 +803,12 @@ MediaInfo\IBlurayExaminer.cs + + MediaInfo\LiveStreamRequest.cs + + + MediaInfo\LiveStreamResponse.cs + MediaInfo\MediaProtocol.cs diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj index b772e17539..3618aa9728 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -759,6 +759,12 @@ MediaInfo\IBlurayExaminer.cs + + MediaInfo\LiveStreamRequest.cs + + + MediaInfo\LiveStreamResponse.cs + MediaInfo\MediaProtocol.cs diff --git a/MediaBrowser.Model/ApiClient/IConnectionManager.cs b/MediaBrowser.Model/ApiClient/IConnectionManager.cs index 84a815dfc0..f8837f15de 100644 --- a/MediaBrowser.Model/ApiClient/IConnectionManager.cs +++ b/MediaBrowser.Model/ApiClient/IConnectionManager.cs @@ -172,5 +172,11 @@ namespace MediaBrowser.Model.ApiClient /// if set to true [remember credentials]. /// Task. Task AuthenticateOffline(UserDto user, string password, bool rememberCredentials); + + /// + /// Gets the offline users. + /// + /// Task<List<UserDto>>. + Task> GetOfflineUsers(); } } diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index fe67038df9..0673095125 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -141,6 +141,8 @@ + + diff --git a/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs b/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs new file mode 100644 index 0000000000..8078219d82 --- /dev/null +++ b/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs @@ -0,0 +1,16 @@ +using MediaBrowser.Model.Dlna; + +namespace MediaBrowser.Model.MediaInfo +{ + public class LiveStreamRequest + { + public string OpenToken { get; set; } + public string UserId { get; set; } + public int? MaxStreamingBitrate { get; set; } + public long? StartTimeTicks { get; set; } + public int? AudioStreamIndex { get; set; } + public int? SubtitleStreamIndex { get; set; } + public string ItemId { get; set; } + public DeviceProfile DeviceProfile { get; set; } + } +} diff --git a/MediaBrowser.Model/MediaInfo/LiveStreamResponse.cs b/MediaBrowser.Model/MediaInfo/LiveStreamResponse.cs new file mode 100644 index 0000000000..e79e37a717 --- /dev/null +++ b/MediaBrowser.Model/MediaInfo/LiveStreamResponse.cs @@ -0,0 +1,9 @@ +using MediaBrowser.Model.Dto; + +namespace MediaBrowser.Model.MediaInfo +{ + public class LiveStreamResponse + { + public MediaSourceInfo MediaSource { get; set; } + } +} diff --git a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs index 3dbcf4aad4..64b1f2c89a 100644 --- a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs +++ b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs @@ -1,5 +1,4 @@ -using System.Collections.Concurrent; -using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; @@ -8,13 +7,14 @@ using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using MediaBrowser.Model.MediaInfo; +using MediaBrowser.Model.Serialization; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Server.Implementations.LiveTv; namespace MediaBrowser.Server.Implementations.Library { @@ -23,16 +23,18 @@ namespace MediaBrowser.Server.Implementations.Library private readonly IItemRepository _itemRepo; private readonly IUserManager _userManager; private readonly ILibraryManager _libraryManager; + private readonly IJsonSerializer _jsonSerializer; private IMediaSourceProvider[] _providers; private readonly ILogger _logger; - public MediaSourceManager(IItemRepository itemRepo, IUserManager userManager, ILibraryManager libraryManager, ILogger logger) + public MediaSourceManager(IItemRepository itemRepo, IUserManager userManager, ILibraryManager libraryManager, ILogger logger, IJsonSerializer jsonSerializer) { _itemRepo = itemRepo; _userManager = userManager; _libraryManager = libraryManager; _logger = logger; + _jsonSerializer = jsonSerializer; } public void AddParts(IEnumerable providers) @@ -317,13 +319,13 @@ namespace MediaBrowser.Server.Implementations.Library private readonly ConcurrentDictionary _openStreams = new ConcurrentDictionary(); private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1); - public async Task OpenLiveStream(string openToken, bool enableAutoClose, CancellationToken cancellationToken) + public async Task OpenLiveStream(LiveStreamRequest request, bool enableAutoClose, CancellationToken cancellationToken) { await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); try { - var tuple = GetProvider(openToken); + var tuple = GetProvider(request.OpenToken); var provider = tuple.Item1; var mediaSource = await provider.OpenMediaSource(tuple.Item2, cancellationToken).ConfigureAwait(false); @@ -344,12 +346,19 @@ namespace MediaBrowser.Server.Implementations.Library StartCloseTimer(); } - if (!string.IsNullOrWhiteSpace(mediaSource.TranscodingUrl)) + var json = _jsonSerializer.SerializeToString(mediaSource); + var clone = _jsonSerializer.DeserializeFromString(json); + + if (!string.IsNullOrWhiteSpace(request.UserId)) { - mediaSource.TranscodingUrl += "&LiveStreamId=" + mediaSource.LiveStreamId; + var user = _userManager.GetUserById(request.UserId); + SetUserProperties(clone, user); } - return mediaSource; + return new LiveStreamResponse + { + MediaSource = clone + }; } finally { diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index ec93ae8766..16fc420639 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -14,6 +14,7 @@ using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Devices; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Events; using MediaBrowser.Model.Library; @@ -304,13 +305,21 @@ namespace MediaBrowser.Server.Implementations.Session } } + private async Task GetMediaSource(BaseItem item, string mediaSourceId) + { + var sources = await _mediaSourceManager.GetPlayackMediaSources(item.Id.ToString("N"), false, CancellationToken.None) + .ConfigureAwait(false); + + return sources.FirstOrDefault(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase)); + } + /// /// Updates the now playing item id. /// /// The session. /// The information. /// The library item. - private void UpdateNowPlayingItem(SessionInfo session, PlaybackProgressInfo info, BaseItem libraryItem) + private async Task UpdateNowPlayingItem(SessionInfo session, PlaybackProgressInfo info, BaseItem libraryItem) { if (string.IsNullOrWhiteSpace(info.MediaSourceId)) { @@ -319,29 +328,27 @@ namespace MediaBrowser.Server.Implementations.Session if (!string.IsNullOrWhiteSpace(info.ItemId) && info.Item == null && libraryItem != null) { - var runtimeTicks = libraryItem.RunTimeTicks; + var current = session.NowPlayingItem; - if (!string.Equals(info.ItemId, info.MediaSourceId) && - !string.IsNullOrWhiteSpace(info.MediaSourceId)) + if (current == null || !string.Equals(current.Id, info.ItemId, StringComparison.OrdinalIgnoreCase)) { - var runtimeItem = _libraryManager.GetItemById(new Guid(info.MediaSourceId)) ?? - _libraryManager.GetItemById(info.ItemId); + var runtimeTicks = libraryItem.RunTimeTicks; - runtimeTicks = runtimeItem.RunTimeTicks; - } + var mediaSource = await GetMediaSource(libraryItem, info.MediaSourceId).ConfigureAwait(false); - var current = session.NowPlayingItem; + if (mediaSource != null) + { + runtimeTicks = mediaSource.RunTimeTicks; + } - if (current == null || !string.Equals(current.Id, info.ItemId, StringComparison.OrdinalIgnoreCase)) - { - info.Item = GetItemInfo(libraryItem, libraryItem, info.MediaSourceId); + info.Item = GetItemInfo(libraryItem, libraryItem, mediaSource); + + info.Item.RunTimeTicks = runtimeTicks; } else { info.Item = current; } - - info.Item.RunTimeTicks = runtimeTicks; } session.NowPlayingItem = info.Item; @@ -432,6 +439,12 @@ namespace MediaBrowser.Server.Implementations.Session device = device ?? _deviceManager.GetDevice(deviceId); + if (device == null) + { + var userIdString = userId.HasValue ? userId.Value.ToString("N") : null; + device = await _deviceManager.RegisterDevice(deviceId, deviceName, appName, appVersion, userIdString).ConfigureAwait(false); + } + if (device != null) { if (!string.IsNullOrEmpty(device.CustomName)) @@ -570,7 +583,7 @@ namespace MediaBrowser.Server.Implementations.Session ? null : _libraryManager.GetItemById(new Guid(info.ItemId)); - UpdateNowPlayingItem(session, info, libraryItem); + await UpdateNowPlayingItem(session, info, libraryItem).ConfigureAwait(false); if (!string.IsNullOrEmpty(session.DeviceId) && info.PlayMethod != PlayMethod.Transcode) { @@ -652,7 +665,7 @@ namespace MediaBrowser.Server.Implementations.Session ? null : _libraryManager.GetItemById(new Guid(info.ItemId)); - UpdateNowPlayingItem(session, info, libraryItem); + await UpdateNowPlayingItem(session, info, libraryItem).ConfigureAwait(false); var users = GetUsers(session); @@ -731,7 +744,9 @@ namespace MediaBrowser.Server.Implementations.Session if (current == null || !string.Equals(current.Id, info.ItemId, StringComparison.OrdinalIgnoreCase)) { - info.Item = GetItemInfo(libraryItem, libraryItem, info.MediaSourceId); + var mediaSource = await GetMediaSource(libraryItem, info.MediaSourceId).ConfigureAwait(false); + + info.Item = GetItemInfo(libraryItem, libraryItem, mediaSource); } else { @@ -1439,10 +1454,10 @@ namespace MediaBrowser.Server.Implementations.Session /// /// The item. /// The chapter owner. - /// The media source identifier. + /// The media source. /// BaseItemInfo. /// item - private BaseItemInfo GetItemInfo(BaseItem item, BaseItem chapterOwner, string mediaSourceId) + private BaseItemInfo GetItemInfo(BaseItem item, BaseItem chapterOwner, MediaSourceInfo mediaSource) { if (item == null) { @@ -1593,9 +1608,9 @@ namespace MediaBrowser.Server.Implementations.Session info.Chapters = _dtoService.GetChapterInfoDtos(chapterOwner).ToList(); } - if (!string.IsNullOrWhiteSpace(mediaSourceId)) + if (mediaSource != null) { - info.MediaStreams = _mediaSourceManager.GetMediaStreams(mediaSourceId).ToList(); + info.MediaStreams = mediaSource.MediaStreams; } return info; diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index 039c5edf38..aad88d022f 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -472,7 +472,7 @@ namespace MediaBrowser.Server.Startup.Common ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, LogManager.GetLogger("ChannelManager"), ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, LocalizationManager, HttpClient); RegisterSingleInstance(ChannelManager); - MediaSourceManager = new MediaSourceManager(ItemRepository, UserManager, LibraryManager, LogManager.GetLogger("MediaSourceManager")); + MediaSourceManager = new MediaSourceManager(ItemRepository, UserManager, LibraryManager, LogManager.GetLogger("MediaSourceManager"), JsonSerializer); RegisterSingleInstance(MediaSourceManager); SessionManager = new SessionManager(UserDataManager, LogManager.GetLogger("SessionManager"), UserRepository, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, JsonSerializer, this, HttpClient, AuthenticationRepository, DeviceManager, MediaSourceManager); From dd8dd1938a990d9c4c9bac384665dc9d82c4bc35 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 29 Mar 2015 14:16:40 -0400 Subject: [PATCH 18/52] update live stream generation --- MediaBrowser.Api/ApiEntryPoint.cs | 2 +- .../Playback/BaseStreamingService.cs | 4 ++++ .../LiveTv/LiveTvChannel.cs | 9 +++++++ MediaBrowser.Model/ApiClient/IApiClient.cs | 7 ++++++ MediaBrowser.Model/Dlna/StreamInfo.cs | 3 +++ .../MediaInfo/LiveStreamRequest.cs | 19 +++++++++++++++ .../Session/PlaybackProgressInfo.cs | 5 ++++ .../Session/PlaybackStopInfo.cs | 5 ++++ .../LiveTv/LiveTvManager.cs | 12 ++++++++-- .../Session/SessionManager.cs | 24 +++++++++++++++++++ Nuget/MediaBrowser.Common.Internal.nuspec | 4 ++-- Nuget/MediaBrowser.Common.nuspec | 2 +- Nuget/MediaBrowser.Model.Signed.nuspec | 2 +- Nuget/MediaBrowser.Server.Core.nuspec | 4 ++-- 14 files changed, 93 insertions(+), 9 deletions(-) diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index a6958b95d3..9f4ad58937 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/MediaBrowser.Api/ApiEntryPoint.cs @@ -187,7 +187,7 @@ namespace MediaBrowser.Api if (!string.IsNullOrWhiteSpace(deviceId)) { - var audioCodec = state.ActualOutputVideoCodec; + var audioCodec = state.ActualOutputAudioCodec; var videoCodec = state.ActualOutputVideoCodec; _sessionManager.ReportTranscodingInfo(deviceId, new TranscodingInfo diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 5a4cdaaa93..34c5e5f7c8 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -1513,6 +1513,10 @@ namespace MediaBrowser.Api.Playback { request.StreamId = val; } + else if (i == 22) + { + request.LiveStreamId = val; + } } } diff --git a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs index 1e13d8f3f4..cb10003ed8 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs @@ -82,6 +82,15 @@ namespace MediaBrowser.Controller.LiveTv /// null if [has image] contains no value, true if [has image]; otherwise, false. public bool? HasProviderImage { get; set; } + public override LocationType LocationType + { + get + { + // TODO: This should be removed + return LocationType.Remote; + } + } + protected override string CreateSortName() { double number = 0; diff --git a/MediaBrowser.Model/ApiClient/IApiClient.cs b/MediaBrowser.Model/ApiClient/IApiClient.cs index 19d6acf339..58af016150 100644 --- a/MediaBrowser.Model/ApiClient/IApiClient.cs +++ b/MediaBrowser.Model/ApiClient/IApiClient.cs @@ -1486,5 +1486,12 @@ namespace MediaBrowser.Model.ApiClient /// The query. /// Task<List<RecommendationDto>>. Task> GetMovieRecommendations(MovieRecommendationQuery query); + /// + /// Opens the live stream. + /// + /// The request. + /// The cancellation token. + /// Task<LiveStreamResponse>. + Task OpenLiveStream(LiveStreamRequest request, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index 60615d1da9..dc24627960 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -210,6 +210,9 @@ namespace MediaBrowser.Model.Dlna list.Add(new NameValuePair("StreamId", streamId ?? string.Empty)); list.Add(new NameValuePair("api_key", accessToken ?? string.Empty)); + string liveStreamId = item.MediaSource == null ? null : item.MediaSource.LiveStreamId; + list.Add(new NameValuePair("LiveStreamId", liveStreamId ?? string.Empty)); + return list; } diff --git a/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs b/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs index 8078219d82..563006a4df 100644 --- a/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs +++ b/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs @@ -12,5 +12,24 @@ namespace MediaBrowser.Model.MediaInfo public int? SubtitleStreamIndex { get; set; } public string ItemId { get; set; } public DeviceProfile DeviceProfile { get; set; } + + public LiveStreamRequest() + { + + } + + public LiveStreamRequest(AudioOptions options) + { + MaxStreamingBitrate = options.MaxBitrate; + ItemId = options.ItemId; + DeviceProfile = options.Profile; + + VideoOptions videoOptions = options as VideoOptions; + if (videoOptions != null) + { + AudioStreamIndex = videoOptions.AudioStreamIndex; + SubtitleStreamIndex = videoOptions.SubtitleStreamIndex; + } + } } } diff --git a/MediaBrowser.Model/Session/PlaybackProgressInfo.cs b/MediaBrowser.Model/Session/PlaybackProgressInfo.cs index f04dea1eac..a029df4a93 100644 --- a/MediaBrowser.Model/Session/PlaybackProgressInfo.cs +++ b/MediaBrowser.Model/Session/PlaybackProgressInfo.cs @@ -78,5 +78,10 @@ namespace MediaBrowser.Model.Session /// /// The play method. public PlayMethod PlayMethod { get; set; } + /// + /// Gets or sets the live stream identifier. + /// + /// The live stream identifier. + public string LiveStreamId { get; set; } } } \ No newline at end of file diff --git a/MediaBrowser.Model/Session/PlaybackStopInfo.cs b/MediaBrowser.Model/Session/PlaybackStopInfo.cs index 38025f1832..a3bdc9a960 100644 --- a/MediaBrowser.Model/Session/PlaybackStopInfo.cs +++ b/MediaBrowser.Model/Session/PlaybackStopInfo.cs @@ -36,5 +36,10 @@ namespace MediaBrowser.Model.Session /// /// The position ticks. public long? PositionTicks { get; set; } + /// + /// Gets or sets the live stream identifier. + /// + /// The live stream identifier. + public string LiveStreamId { get; set; } } } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index 86b31e0e5d..b30db60927 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -356,7 +356,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv _logger.Info("Opening channel stream from {0}, external channel Id: {1}", service.Name, channel.ExternalId); info = await service.GetChannelStream(channel.ExternalId, null, cancellationToken).ConfigureAwait(false); info.RequiresClosing = true; - info.LiveStreamId = info.Id; + + if (info.RequiresClosing) + { + info.LiveStreamId = info.Id; + } } else { @@ -367,7 +371,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv _logger.Info("Opening recording stream from {0}, external recording Id: {1}", service.Name, recording.RecordingInfo.Id); info = await service.GetRecordingStream(recording.RecordingInfo.Id, null, cancellationToken).ConfigureAwait(false); info.RequiresClosing = true; - info.LiveStreamId = info.Id; + + if (info.RequiresClosing) + { + info.LiveStreamId = info.Id; + } } _logger.Info("Live stream info: {0}", _jsonSerializer.SerializeToString(info)); diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index 16fc420639..cc5aef54aa 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -679,6 +679,18 @@ namespace MediaBrowser.Server.Implementations.Session } } + if (!string.IsNullOrWhiteSpace(info.LiveStreamId)) + { + try + { + await _mediaSourceManager.PingLiveStream(info.LiveStreamId, CancellationToken.None).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.ErrorException("Error closing live stream", ex); + } + } + EventHelper.FireEventIfNotNull(PlaybackProgress, this, new PlaybackProgressEventArgs { Item = libraryItem, @@ -769,6 +781,18 @@ namespace MediaBrowser.Server.Implementations.Session } } + if (!string.IsNullOrWhiteSpace(info.LiveStreamId)) + { + try + { + await _mediaSourceManager.CloseLiveStream(info.LiveStreamId, CancellationToken.None).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.ErrorException("Error closing live stream", ex); + } + } + EventHelper.QueueEventIfNotNull(PlaybackStopped, this, new PlaybackStopEventArgs { Item = libraryItem, diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index e1659bfb28..ee3bdd2b6b 100644 --- a/Nuget/MediaBrowser.Common.Internal.nuspec +++ b/Nuget/MediaBrowser.Common.Internal.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common.Internal - 3.0.603 + 3.0.606 MediaBrowser.Common.Internal Luke ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains common components shared by Emby Theater and Emby Server. Not intended for plugin developer consumption. Copyright © Emby 2013 - + diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index 294bc519eb..a8f3ea1f38 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common - 3.0.603 + 3.0.606 MediaBrowser.Common Emby Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Model.Signed.nuspec b/Nuget/MediaBrowser.Model.Signed.nuspec index bcbd1d5bea..7bd4a090dd 100644 --- a/Nuget/MediaBrowser.Model.Signed.nuspec +++ b/Nuget/MediaBrowser.Model.Signed.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Model.Signed - 3.0.603 + 3.0.606 MediaBrowser.Model - Signed Edition Emby Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index ee3925db5b..3f39ace9dd 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Server.Core - 3.0.603 + 3.0.606 Media Browser.Server.Core Emby Team ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains core components required to build plugins for Emby Server. Copyright © Emby 2013 - + From 10b9a865b7baed2fb83ec7a71e0524ffe80ea609 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 29 Mar 2015 14:31:28 -0400 Subject: [PATCH 19/52] update live stream handling --- MediaBrowser.Api/ApiEntryPoint.cs | 18 +++++++++--------- .../Playback/BaseStreamingService.cs | 6 +++--- .../Playback/Dash/MpegDashService.cs | 2 +- .../Playback/Hls/DynamicHlsService.cs | 2 +- .../Playback/Hls/HlsSegmentService.cs | 6 +++--- MediaBrowser.Api/Playback/MediaInfoService.cs | 2 +- MediaBrowser.Api/Playback/StreamRequest.cs | 2 +- MediaBrowser.Model/Dlna/StreamInfo.cs | 4 ++-- .../MediaInfo/PlaybackInfoResponse.cs | 6 +++--- Nuget/MediaBrowser.Common.Internal.nuspec | 4 ++-- Nuget/MediaBrowser.Common.nuspec | 2 +- Nuget/MediaBrowser.Model.Signed.nuspec | 2 +- Nuget/MediaBrowser.Server.Core.nuspec | 4 ++-- 13 files changed, 30 insertions(+), 30 deletions(-) diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index 9f4ad58937..444977e352 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/MediaBrowser.Api/ApiEntryPoint.cs @@ -132,7 +132,7 @@ namespace MediaBrowser.Api /// Called when [transcode beginning]. /// /// The path. - /// The stream identifier. + /// The play session identifier. /// The transcoding job identifier. /// The type. /// The process. @@ -141,7 +141,7 @@ namespace MediaBrowser.Api /// The cancellation token source. /// TranscodingJob. public TranscodingJob OnTranscodeBeginning(string path, - string streamId, + string playSessionId, string transcodingJobId, TranscodingJobType type, Process process, @@ -160,7 +160,7 @@ namespace MediaBrowser.Api DeviceId = deviceId, CancellationTokenSource = cancellationTokenSource, Id = transcodingJobId, - StreamId = streamId + PlaySessionId = playSessionId }; _activeTranscodingJobs.Add(job); @@ -324,10 +324,10 @@ namespace MediaBrowser.Api /// Kills the single transcoding job. /// /// The device id. - /// The stream identifier. + /// The play session identifier. /// The delete files. /// Task. - internal void KillTranscodingJobs(string deviceId, string streamId, Func deleteFiles) + internal void KillTranscodingJobs(string deviceId, string playSessionId, Func deleteFiles) { if (string.IsNullOrEmpty(deviceId)) { @@ -338,7 +338,7 @@ namespace MediaBrowser.Api { if (string.Equals(deviceId, j.DeviceId, StringComparison.OrdinalIgnoreCase)) { - return string.IsNullOrWhiteSpace(streamId) || string.Equals(streamId, j.StreamId, StringComparison.OrdinalIgnoreCase); + return string.IsNullOrWhiteSpace(playSessionId) || string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase); } return false; @@ -539,10 +539,10 @@ namespace MediaBrowser.Api public class TranscodingJob { /// - /// Gets or sets the stream identifier. + /// Gets or sets the play session identifier. /// - /// The stream identifier. - public string StreamId { get; set; } + /// The play session identifier. + public string PlaySessionId { get; set; } /// /// Gets or sets the path. /// diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 34c5e5f7c8..c4cdfc9edd 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -126,7 +126,7 @@ namespace MediaBrowser.Api.Playback var data = GetCommandLineArguments("dummy\\dummy", state, false); data += "-" + (state.Request.DeviceId ?? string.Empty); - data += "-" + (state.Request.StreamId ?? string.Empty); + data += "-" + (state.Request.PlaySessionId ?? string.Empty); data += "-" + (state.Request.ClientTime ?? string.Empty); var dataHash = data.GetMD5().ToString("N"); @@ -1009,7 +1009,7 @@ namespace MediaBrowser.Api.Playback } var transcodingJob = ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath, - state.Request.StreamId, + state.Request.PlaySessionId, transcodingId, TranscodingJobType, process, @@ -1511,7 +1511,7 @@ namespace MediaBrowser.Api.Playback } else if (i == 21) { - request.StreamId = val; + request.PlaySessionId = val; } else if (i == 22) { diff --git a/MediaBrowser.Api/Playback/Dash/MpegDashService.cs b/MediaBrowser.Api/Playback/Dash/MpegDashService.cs index 692e8d4e74..ba3f172579 100644 --- a/MediaBrowser.Api/Playback/Dash/MpegDashService.cs +++ b/MediaBrowser.Api/Playback/Dash/MpegDashService.cs @@ -160,7 +160,7 @@ namespace MediaBrowser.Api.Playback.Dash // If the playlist doesn't already exist, startup ffmpeg try { - ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.StreamId, p => false); + ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.PlaySessionId, p => false); if (currentTranscodingIndex.HasValue) { diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index 4f7c0444b2..4f043d3217 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -135,7 +135,7 @@ namespace MediaBrowser.Api.Playback.Hls // If the playlist doesn't already exist, startup ffmpeg try { - ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.StreamId, p => false); + ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.PlaySessionId, p => false); if (currentTranscodingIndex.HasValue) { diff --git a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs index 1fe0661d9f..5d8c67abe9 100644 --- a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs +++ b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs @@ -54,8 +54,8 @@ namespace MediaBrowser.Api.Playback.Hls [ApiMember(Name = "DeviceId", Description = "The device id of the client requesting. Used to stop encoding processes when needed.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")] public string DeviceId { get; set; } - [ApiMember(Name = "StreamId", Description = "The stream id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")] - public string StreamId { get; set; } + [ApiMember(Name = "PlaySessionId", Description = "The play session id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")] + public string PlaySessionId { get; set; } } /// @@ -95,7 +95,7 @@ namespace MediaBrowser.Api.Playback.Hls public void Delete(StopEncodingProcess request) { - ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.StreamId, path => true); + ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.PlaySessionId, path => true); } /// diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs index 6f8c2cc505..c6678d1ed1 100644 --- a/MediaBrowser.Api/Playback/MediaInfoService.cs +++ b/MediaBrowser.Api/Playback/MediaInfoService.cs @@ -194,7 +194,7 @@ namespace MediaBrowser.Api.Playback } else { - result.StreamId = Guid.NewGuid().ToString("N"); + result.PlaySessionId = Guid.NewGuid().ToString("N"); } return result; diff --git a/MediaBrowser.Api/Playback/StreamRequest.cs b/MediaBrowser.Api/Playback/StreamRequest.cs index 7ed4fcd965..75242e604c 100644 --- a/MediaBrowser.Api/Playback/StreamRequest.cs +++ b/MediaBrowser.Api/Playback/StreamRequest.cs @@ -71,7 +71,7 @@ namespace MediaBrowser.Api.Playback public string Params { get; set; } public string ClientTime { get; set; } - public string StreamId { get; set; } + public string PlaySessionId { get; set; } public string LiveStreamId { get; set; } } diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index dc24627960..12319a1227 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -206,8 +206,8 @@ namespace MediaBrowser.Model.Dlna list.Add(new NameValuePair("Profile", item.VideoProfile ?? string.Empty)); list.Add(new NameValuePair("Cabac", item.Cabac.HasValue ? item.Cabac.Value.ToString() : string.Empty)); - string streamId = item.PlaybackInfo == null ? null : item.PlaybackInfo.StreamId; - list.Add(new NameValuePair("StreamId", streamId ?? string.Empty)); + string playSessionId = item.PlaybackInfo == null ? null : item.PlaybackInfo.PlaySessionId; + list.Add(new NameValuePair("PlaySessionId", playSessionId ?? string.Empty)); list.Add(new NameValuePair("api_key", accessToken ?? string.Empty)); string liveStreamId = item.MediaSource == null ? null : item.MediaSource.LiveStreamId; diff --git a/MediaBrowser.Model/MediaInfo/PlaybackInfoResponse.cs b/MediaBrowser.Model/MediaInfo/PlaybackInfoResponse.cs index ed0824e8a4..1f8936d018 100644 --- a/MediaBrowser.Model/MediaInfo/PlaybackInfoResponse.cs +++ b/MediaBrowser.Model/MediaInfo/PlaybackInfoResponse.cs @@ -13,10 +13,10 @@ namespace MediaBrowser.Model.MediaInfo public List MediaSources { get; set; } /// - /// Gets or sets the stream identifier. + /// Gets or sets the play session identifier. /// - /// The stream identifier. - public string StreamId { get; set; } + /// The play session identifier. + public string PlaySessionId { get; set; } /// /// Gets or sets the error code. diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index ee3bdd2b6b..57a1db722d 100644 --- a/Nuget/MediaBrowser.Common.Internal.nuspec +++ b/Nuget/MediaBrowser.Common.Internal.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common.Internal - 3.0.606 + 3.0.607 MediaBrowser.Common.Internal Luke ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains common components shared by Emby Theater and Emby Server. Not intended for plugin developer consumption. Copyright © Emby 2013 - + diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index a8f3ea1f38..9ad9c18ca2 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common - 3.0.606 + 3.0.607 MediaBrowser.Common Emby Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Model.Signed.nuspec b/Nuget/MediaBrowser.Model.Signed.nuspec index 7bd4a090dd..261ddd37d1 100644 --- a/Nuget/MediaBrowser.Model.Signed.nuspec +++ b/Nuget/MediaBrowser.Model.Signed.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Model.Signed - 3.0.606 + 3.0.607 MediaBrowser.Model - Signed Edition Emby Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index 3f39ace9dd..49a38aaeb3 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Server.Core - 3.0.606 + 3.0.607 Media Browser.Server.Core Emby Team ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains core components required to build plugins for Emby Server. Copyright © Emby 2013 - + From 66ea0b256686b15f01a062cc5deaaf445905f4fb Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 29 Mar 2015 14:45:48 -0400 Subject: [PATCH 20/52] updated nuget --- Nuget/MediaBrowser.Common.Internal.nuspec | 4 ++-- Nuget/MediaBrowser.Common.nuspec | 2 +- Nuget/MediaBrowser.Model.Signed.nuspec | 2 +- Nuget/MediaBrowser.Server.Core.nuspec | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index 57a1db722d..dd28aae3ac 100644 --- a/Nuget/MediaBrowser.Common.Internal.nuspec +++ b/Nuget/MediaBrowser.Common.Internal.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common.Internal - 3.0.607 + 3.0.608 MediaBrowser.Common.Internal Luke ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains common components shared by Emby Theater and Emby Server. Not intended for plugin developer consumption. Copyright © Emby 2013 - + diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index 9ad9c18ca2..0124a609de 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common - 3.0.607 + 3.0.608 MediaBrowser.Common Emby Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Model.Signed.nuspec b/Nuget/MediaBrowser.Model.Signed.nuspec index 261ddd37d1..3297b66066 100644 --- a/Nuget/MediaBrowser.Model.Signed.nuspec +++ b/Nuget/MediaBrowser.Model.Signed.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Model.Signed - 3.0.607 + 3.0.608 MediaBrowser.Model - Signed Edition Emby Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index 49a38aaeb3..0a749c98b6 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Server.Core - 3.0.607 + 3.0.608 Media Browser.Server.Core Emby Team ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains core components required to build plugins for Emby Server. Copyright © Emby 2013 - + From 5474215141d92c4012f387d6d9ebe9116ca74abc Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 29 Mar 2015 18:38:32 -0400 Subject: [PATCH 21/52] sync updates --- MediaBrowser.Api/Playback/MediaInfoService.cs | 12 ++++++++---- .../Library/MediaSourceManager.cs | 14 +++++++++++++- .../Sync/MediaSync.cs | 8 ++++---- .../Sync/SyncManager.cs | 2 +- .../Sync/SyncedMediaSourceProvider.cs | 11 ++++++++--- 5 files changed, 34 insertions(+), 13 deletions(-) diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs index c6678d1ed1..b833dd7350 100644 --- a/MediaBrowser.Api/Playback/MediaInfoService.cs +++ b/MediaBrowser.Api/Playback/MediaInfoService.cs @@ -119,12 +119,16 @@ namespace MediaBrowser.Api.Playback { var item = _libraryManager.GetItemById(request.ItemId); - SetDeviceSpecificData(item, result.MediaSource, profile, authInfo, request.MaxStreamingBitrate, request.StartTimeTicks ?? 0, result.MediaSource.Id, request.AudioStreamIndex, request.SubtitleStreamIndex); + SetDeviceSpecificData(item, result.MediaSource, profile, authInfo, request.MaxStreamingBitrate, + request.StartTimeTicks ?? 0, result.MediaSource.Id, request.AudioStreamIndex, + request.SubtitleStreamIndex); } - - if (!string.IsNullOrWhiteSpace(result.MediaSource.TranscodingUrl)) + else { - result.MediaSource.TranscodingUrl += "&LiveStreamId=" + result.MediaSource.LiveStreamId; + if (!string.IsNullOrWhiteSpace(result.MediaSource.TranscodingUrl)) + { + result.MediaSource.TranscodingUrl += "&LiveStreamId=" + result.MediaSource.LiveStreamId; + } } return ToOptimizedResult(result); diff --git a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs index 64b1f2c89a..e832142a90 100644 --- a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs +++ b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs @@ -316,7 +316,7 @@ namespace MediaBrowser.Server.Implementations.Library return GetStaticMediaSources(item, enablePathSubstitution).FirstOrDefault(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase)); } - private readonly ConcurrentDictionary _openStreams = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _openStreams = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1); public async Task OpenLiveStream(LiveStreamRequest request, bool enableAutoClose, CancellationToken cancellationToken) @@ -330,6 +330,11 @@ namespace MediaBrowser.Server.Implementations.Library var mediaSource = await provider.OpenMediaSource(tuple.Item2, cancellationToken).ConfigureAwait(false); + if (string.IsNullOrWhiteSpace(mediaSource.LiveStreamId)) + { + throw new InvalidOperationException(string.Format("{0} returned null LiveStreamId", provider.GetType().Name)); + } + SetKeyProperties(provider, mediaSource); var info = new LiveStreamInfo @@ -368,6 +373,13 @@ namespace MediaBrowser.Server.Implementations.Library public async Task GetLiveStream(string id, CancellationToken cancellationToken) { + if (string.IsNullOrWhiteSpace(id)) + { + throw new ArgumentNullException("id"); + } + + _logger.Debug("Getting live stream {0}", id); + await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); try diff --git a/MediaBrowser.Server.Implementations/Sync/MediaSync.cs b/MediaBrowser.Server.Implementations/Sync/MediaSync.cs index dd8ce82ef1..620143f175 100644 --- a/MediaBrowser.Server.Implementations/Sync/MediaSync.cs +++ b/MediaBrowser.Server.Implementations/Sync/MediaSync.cs @@ -371,7 +371,7 @@ namespace MediaBrowser.Server.Implementations.Sync if (item.IsType("episode")) { - parts.Add("TV"); + //parts.Add("TV"); if (!string.IsNullOrWhiteSpace(item.SeriesName)) { parts.Add(item.SeriesName); @@ -379,12 +379,12 @@ namespace MediaBrowser.Server.Implementations.Sync } else if (item.IsVideo) { - parts.Add("Videos"); + //parts.Add("Videos"); parts.Add(item.Name); } else if (item.IsAudio) { - parts.Add("Music"); + //parts.Add("Music"); if (!string.IsNullOrWhiteSpace(item.AlbumArtist)) { @@ -398,7 +398,7 @@ namespace MediaBrowser.Server.Implementations.Sync } else if (string.Equals(item.MediaType, MediaType.Photo, StringComparison.OrdinalIgnoreCase)) { - parts.Add("Photos"); + //parts.Add("Photos"); if (!string.IsNullOrWhiteSpace(item.Album)) { diff --git a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs index 2cf6c68539..d1ebbd28e3 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs @@ -721,7 +721,7 @@ namespace MediaBrowser.Server.Implementations.Sync var jobItemResult = GetJobItems(new SyncJobItemQuery { TargetId = targetId, - Statuses = new SyncJobItemStatus[] + Statuses = new[] { SyncJobItemStatus.ReadyToTransfer } diff --git a/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs b/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs index 1c17b99936..d1ef523e14 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs @@ -92,6 +92,8 @@ namespace MediaBrowser.Server.Implementations.Sync keyList.Add(item.Id); mediaSource.OpenToken = string.Join("|", keyList.ToArray()); } + + list.Add(mediaSource); } public async Task OpenMediaSource(string openToken, CancellationToken cancellationToken) @@ -111,13 +113,16 @@ namespace MediaBrowser.Server.Implementations.Sync var dynamicInfo = await requiresDynamicAccess.GetSyncedFileInfo(localItem.LocalPath, target, cancellationToken).ConfigureAwait(false); var mediaSource = localItem.Item.MediaSources.First(); + mediaSource.LiveStreamId = Guid.NewGuid().ToString(); SetStaticMediaSourceInfo(localItem, mediaSource); foreach (var stream in mediaSource.MediaStreams) { - var dynamicStreamInfo = await requiresDynamicAccess.GetSyncedFileInfo(stream.ExternalId, target, cancellationToken).ConfigureAwait(false); - - stream.Path = dynamicStreamInfo.Path; + if (!string.IsNullOrWhiteSpace(stream.ExternalId)) + { + var dynamicStreamInfo = await requiresDynamicAccess.GetSyncedFileInfo(stream.ExternalId, target, cancellationToken).ConfigureAwait(false); + stream.Path = dynamicStreamInfo.Path; + } } mediaSource.Path = dynamicInfo.Path; From e035e032d07711ee9a30ce8c554e7f386afadcd7 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 29 Mar 2015 19:20:52 -0400 Subject: [PATCH 22/52] 3.0.5557.40000 --- SharedVersion.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SharedVersion.cs b/SharedVersion.cs index 8019ca0e33..fca0540258 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,4 +1,4 @@ using System.Reflection; -[assembly: AssemblyVersion("3.0.*")] -//[assembly: AssemblyVersion("3.0.5557.30000")] +//[assembly: AssemblyVersion("3.0.*")] +[assembly: AssemblyVersion("3.0.5557.40000")] From 7f537ad149e2dc4407229be7c2fcd2df34b8f288 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 29 Mar 2015 19:47:31 -0400 Subject: [PATCH 23/52] sync updates --- .../Library/MediaSourceManager.cs | 9 ++++++++- .../Sync/MediaSync.cs | 17 ++++++++++------- .../Sync/SyncManager.cs | 12 +++++++----- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs index e832142a90..c752fe48e9 100644 --- a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs +++ b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs @@ -430,7 +430,14 @@ namespace MediaBrowser.Server.Implementations.Library { var tuple = GetProvider(id); - await tuple.Item1.CloseMediaSource(tuple.Item2, cancellationToken).ConfigureAwait(false); + try + { + await tuple.Item1.CloseMediaSource(tuple.Item2, cancellationToken).ConfigureAwait(false); + } + catch (NotImplementedException) + { + + } LiveStreamInfo removed; if (_openStreams.TryRemove(id, out removed)) diff --git a/MediaBrowser.Server.Implementations/Sync/MediaSync.cs b/MediaBrowser.Server.Implementations/Sync/MediaSync.cs index 620143f175..ae8b228275 100644 --- a/MediaBrowser.Server.Implementations/Sync/MediaSync.cs +++ b/MediaBrowser.Server.Implementations/Sync/MediaSync.cs @@ -40,6 +40,7 @@ namespace MediaBrowser.Server.Implementations.Sync CancellationToken cancellationToken) { var serverId = _appHost.SystemId; + var serverName = _appHost.FriendlyName; await SyncData(provider, dataProvider, serverId, target, cancellationToken).ConfigureAwait(false); progress.Report(3); @@ -51,7 +52,7 @@ namespace MediaBrowser.Server.Implementations.Sync totalProgress += 1; progress.Report(totalProgress); }); - await GetNewMedia(provider, dataProvider, target, serverId, innerProgress, cancellationToken); + await GetNewMedia(provider, dataProvider, target, serverId, serverName, innerProgress, cancellationToken); // Do the data sync twice so the server knows what was removed from the device await SyncData(provider, dataProvider, serverId, target, cancellationToken).ConfigureAwait(false); @@ -93,6 +94,7 @@ namespace MediaBrowser.Server.Implementations.Sync ISyncDataProvider dataProvider, SyncTarget target, string serverId, + string serverName, IProgress progress, CancellationToken cancellationToken) { @@ -119,7 +121,7 @@ namespace MediaBrowser.Server.Implementations.Sync progress.Report(totalProgress); }); - await GetItem(provider, dataProvider, target, serverId, jobItem, innerProgress, cancellationToken).ConfigureAwait(false); + await GetItem(provider, dataProvider, target, serverId, serverName, jobItem, innerProgress, cancellationToken).ConfigureAwait(false); numComplete++; startingPercent = numComplete; @@ -133,6 +135,7 @@ namespace MediaBrowser.Server.Implementations.Sync ISyncDataProvider dataProvider, SyncTarget target, string serverId, + string serverName, SyncedItem jobItem, IProgress progress, CancellationToken cancellationToken) @@ -140,7 +143,7 @@ namespace MediaBrowser.Server.Implementations.Sync var libraryItem = jobItem.Item; var internalSyncJobItem = _syncManager.GetJobItem(jobItem.SyncJobItemId); - var localItem = CreateLocalItem(provider, jobItem, target, libraryItem, serverId, jobItem.OriginalFileName); + var localItem = CreateLocalItem(provider, jobItem, target, libraryItem, serverId, serverName, jobItem.OriginalFileName); await _syncManager.ReportSyncJobItemTransferBeginning(internalSyncJobItem.Id); @@ -326,9 +329,9 @@ namespace MediaBrowser.Server.Implementations.Sync } } - public LocalItem CreateLocalItem(IServerSyncProvider provider, SyncedItem syncedItem, SyncTarget target, BaseItemDto libraryItem, string serverId, string originalFileName) + public LocalItem CreateLocalItem(IServerSyncProvider provider, SyncedItem syncedItem, SyncTarget target, BaseItemDto libraryItem, string serverId, string serverName, string originalFileName) { - var path = GetDirectoryPath(provider, syncedItem, libraryItem, serverId); + var path = GetDirectoryPath(provider, syncedItem, libraryItem, serverId, serverName); path.Add(GetLocalFileName(provider, libraryItem, originalFileName)); var localPath = provider.GetFullPath(path, target); @@ -361,11 +364,11 @@ namespace MediaBrowser.Server.Implementations.Sync return name; } - private List GetDirectoryPath(IServerSyncProvider provider, SyncedItem syncedItem, BaseItemDto item, string serverId) + private List GetDirectoryPath(IServerSyncProvider provider, SyncedItem syncedItem, BaseItemDto item, string serverId, string serverName) { var parts = new List { - serverId, + serverName, GetSyncJobFolderName(syncedItem, provider) }; diff --git a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs index d1ebbd28e3..5e5a0d2fc7 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs @@ -676,14 +676,16 @@ namespace MediaBrowser.Server.Implementations.Sync syncedItem.Item.MediaSources = new List(); - // This will be null for items that are not audio/video - if (mediaSource == null) + syncedItem.OriginalFileName = Path.GetFileName(libraryItem.Path); + + if (string.IsNullOrWhiteSpace(syncedItem.OriginalFileName)) { - syncedItem.OriginalFileName = Path.GetFileName(libraryItem.Path); + syncedItem.OriginalFileName = Path.GetFileName(mediaSource.Path); } - else + + // This will be null for items that are not audio/video + if (mediaSource != null) { - syncedItem.OriginalFileName = Path.GetFileName(mediaSource.Path); syncedItem.Item.MediaSources.Add(mediaSource); } From 0bd27381e0ef5a0a17ea93bb86a752c5d2cc2e1a Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 29 Mar 2015 23:04:55 -0400 Subject: [PATCH 24/52] sync updates --- .../Net/BasePeriodicWebSocketListener.cs | 7 ++- .../Library/MediaSourceManager.cs | 9 +--- .../LiveTv/ChannelImageProvider.cs | 6 ++- .../LiveTv/LiveTvManager.cs | 52 ++++++++++++------- SharedVersion.cs | 4 +- 5 files changed, 42 insertions(+), 36 deletions(-) diff --git a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs index 0afaf955e9..3756f1d938 100644 --- a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs +++ b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs @@ -1,5 +1,4 @@ -using MediaBrowser.Common.Net; -using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Logging; using MediaBrowser.Model.Net; using System; using System.Collections.Generic; @@ -75,12 +74,12 @@ namespace MediaBrowser.Controller.Net throw new ArgumentNullException("message"); } - if (message.MessageType.Equals(Name + "Start", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(message.MessageType, Name + "Start", StringComparison.OrdinalIgnoreCase)) { Start(message); } - if (message.MessageType.Equals(Name + "Stop", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(message.MessageType, Name + "Stop", StringComparison.OrdinalIgnoreCase)) { Stop(message); } diff --git a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs index c752fe48e9..e832142a90 100644 --- a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs +++ b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs @@ -430,14 +430,7 @@ namespace MediaBrowser.Server.Implementations.Library { var tuple = GetProvider(id); - try - { - await tuple.Item1.CloseMediaSource(tuple.Item2, cancellationToken).ConfigureAwait(false); - } - catch (NotImplementedException) - { - - } + await tuple.Item1.CloseMediaSource(tuple.Item2, cancellationToken).ConfigureAwait(false); LiveStreamInfo removed; if (_openStreams.TryRemove(id, out removed)) diff --git a/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs index ed67df01e4..7c3af0a54f 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs @@ -51,11 +51,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv var response = await _httpClient.GetResponse(options).ConfigureAwait(false); - if (response.ContentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase)) + var contentType = response.ContentType; + + if (contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase)) { imageResponse.HasImage = true; imageResponse.Stream = response.Content; - imageResponse.SetFormatFromMimeType(response.ContentType); + imageResponse.SetFormatFromMimeType(contentType); } else { diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index b30db60927..f676918593 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -85,8 +85,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv get { return _services; } } - public ILiveTvService ActiveService { get; private set; } - private LiveTvOptions GetConfiguration() { return _config.GetConfiguration("livetv"); @@ -100,8 +98,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv { _services.AddRange(services); - ActiveService = _services.FirstOrDefault(); - foreach (var service in _services) { service.DataSourceChanged += service_DataSourceChanged; @@ -359,7 +355,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv if (info.RequiresClosing) { - info.LiveStreamId = info.Id; + var idPrefix = service.GetType().FullName.GetMD5().ToString("N") + "_"; + + info.LiveStreamId = idPrefix + info.Id; } } else @@ -374,7 +372,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv if (info.RequiresClosing) { - info.LiveStreamId = info.Id; + var idPrefix = service.GetType().FullName.GetMD5().ToString("N") + "_"; + + info.LiveStreamId = idPrefix + info.Id; } } @@ -384,7 +384,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv var data = new LiveStreamData { Info = info, - ConsumerCount = 1, IsChannel = isChannel, ItemId = id }; @@ -1788,7 +1787,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv class LiveStreamData { internal MediaSourceInfo Info; - internal int ConsumerCount; internal string ItemId; internal bool IsChannel; } @@ -1799,19 +1797,18 @@ namespace MediaBrowser.Server.Implementations.LiveTv try { - var service = ActiveService; + var parts = id.Split(new[] { '_' }, 2); - LiveStreamData data; - if (_openStreams.TryGetValue(id, out data)) - { - if (data.ConsumerCount > 1) - { - data.ConsumerCount--; - _logger.Info("Decrementing live stream client count."); - return; - } + var service = _services.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N"), parts[0], StringComparison.OrdinalIgnoreCase)); + if (service == null) + { + throw new ArgumentException("Service not found."); } + + id = parts[1]; + + LiveStreamData data; _openStreams.TryRemove(id, out data); _logger.Info("Closing live stream from {0}, stream Id: {1}", service.Name, id); @@ -1892,6 +1889,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv Name = service.Name }; + var tunerIdPrefix = service.GetType().FullName.GetMD5().ToString("N") + "_"; + try { var statusInfo = await service.GetStatusInfoAsync(cancellationToken).ConfigureAwait(false); @@ -1913,7 +1912,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv channelName = channel == null ? null : channel.Name; } - return _tvDtoService.GetTunerInfoDto(service.Name, i, channelName); + var dto = _tvDtoService.GetTunerInfoDto(service.Name, i, channelName); + + dto.Id = tunerIdPrefix + dto.Id; + + return dto; }).ToList(); } @@ -1966,7 +1969,16 @@ namespace MediaBrowser.Server.Implementations.LiveTv /// Task. public Task ResetTuner(string id, CancellationToken cancellationToken) { - return ActiveService.ResetTuner(id, cancellationToken); + var parts = id.Split(new[] { '_' }, 2); + + var service = _services.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N"), parts[0], StringComparison.OrdinalIgnoreCase)); + + if (service == null) + { + throw new ArgumentException("Service not found."); + } + + return service.ResetTuner(parts[1], cancellationToken); } public async Task GetLiveTvFolder(string userId, CancellationToken cancellationToken) diff --git a/SharedVersion.cs b/SharedVersion.cs index fca0540258..d744ccd55f 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,4 +1,4 @@ using System.Reflection; -//[assembly: AssemblyVersion("3.0.*")] -[assembly: AssemblyVersion("3.0.5557.40000")] +[assembly: AssemblyVersion("3.0.*")] +//[assembly: AssemblyVersion("3.0.5557.40000")] From 5f044cfd68eecf116df1a646806fc091f3fb63aa Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 30 Mar 2015 12:16:34 -0400 Subject: [PATCH 25/52] add setting to control transcodng throttle --- .../Playback/BaseStreamingService.cs | 11 +- MediaBrowser.Api/Playback/StreamState.cs | 36 ++++ .../Playback/TranscodingThrottler.cs | 26 ++- MediaBrowser.Dlna/Didl/DidlBuilder.cs | 8 +- MediaBrowser.Dlna/PlayTo/PlayToController.cs | 4 +- .../Encoder/AudioEncoder.cs | 2 +- .../Encoder/BaseEncoder.cs | 81 ++------ .../Encoder/EncodingJob.cs | 54 +++++- .../Encoder/EncodingJobFactory.cs | 181 +++++++----------- .../Encoder/MediaEncoder.cs | 4 - .../Encoder/VideoEncoder.cs | 2 +- .../Configuration/EncodingOptions.cs | 4 + MediaBrowser.Model/Dlna/ConditionProcessor.cs | 13 +- .../Dlna/ContentFeatureBuilder.cs | 8 +- MediaBrowser.Model/Dlna/DeviceProfile.cs | 6 +- .../Dlna/ProfileConditionValue.cs | 5 +- MediaBrowser.Model/Dlna/StreamBuilder.cs | 13 +- MediaBrowser.Model/Dlna/StreamInfo.cs | 36 ++++ MediaBrowser.Model/Dto/MediaSourceInfo.cs | 34 +++- .../Localization/JavaScript/javascript.json | 3 +- .../Localization/Server/server.json | 4 +- .../Sync/CloudSyncProfile.cs | 145 +++++++++++++- .../Api/PackageCreator.cs | 2 +- .../MediaBrowser.WebDashboard.csproj | 4 +- Nuget/MediaBrowser.Common.Internal.nuspec | 4 +- Nuget/MediaBrowser.Common.nuspec | 2 +- Nuget/MediaBrowser.Model.Signed.nuspec | 2 +- Nuget/MediaBrowser.Server.Core.nuspec | 4 +- 28 files changed, 466 insertions(+), 232 deletions(-) diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index c4cdfc9edd..8c4bcf0a31 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -1080,7 +1080,7 @@ namespace MediaBrowser.Api.Playback { if (state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks && state.IsInputVideo) { - transcodingJob.TranscodingThrottler = state.TranscodingThrottler = new TranscodingThrottler(transcodingJob, Logger); + transcodingJob.TranscodingThrottler = state.TranscodingThrottler = new TranscodingThrottler(transcodingJob, Logger, ServerConfigurationManager); state.TranscodingThrottler.Start(); } } @@ -2012,7 +2012,6 @@ namespace MediaBrowser.Api.Playback } var audioCodec = state.ActualOutputAudioCodec; - var videoCodec = state.ActualOutputVideoCodec; var mediaProfile = state.VideoRequest == null ? @@ -2033,7 +2032,9 @@ namespace MediaBrowser.Api.Playback state.TargetTimestamp, state.IsTargetAnamorphic, state.IsTargetCabac, - state.TargetRefFrames); + state.TargetRefFrames, + state.TargetVideoStreamCount, + state.TargetAudioStreamCount); if (mediaProfile != null) { @@ -2118,7 +2119,9 @@ namespace MediaBrowser.Api.Playback state.TranscodeSeekInfo, state.IsTargetAnamorphic, state.IsTargetCabac, - state.TargetRefFrames + state.TargetRefFrames, + state.TargetVideoStreamCount, + state.TargetAudioStreamCount ).FirstOrDefault() ?? string.Empty; } diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs index b097f3b6af..2d1e896db0 100644 --- a/MediaBrowser.Api/Playback/StreamState.cs +++ b/MediaBrowser.Api/Playback/StreamState.cs @@ -346,6 +346,42 @@ namespace MediaBrowser.Api.Playback } } + public int? TargetVideoStreamCount + { + get + { + if (Request.Static) + { + return GetMediaStreamCount(MediaStreamType.Video, int.MaxValue); + } + return GetMediaStreamCount(MediaStreamType.Video, 1); + } + } + + public int? TargetAudioStreamCount + { + get + { + if (Request.Static) + { + return GetMediaStreamCount(MediaStreamType.Audio, int.MaxValue); + } + return GetMediaStreamCount(MediaStreamType.Audio, 1); + } + } + + private int? GetMediaStreamCount(MediaStreamType type, int limit) + { + var count = MediaSource.GetStreamCount(type); + + if (count.HasValue) + { + count = Math.Min(count.Value, limit); + } + + return count; + } + /// /// Predicts the audio sample rate that will be in the output stream /// diff --git a/MediaBrowser.Api/Playback/TranscodingThrottler.cs b/MediaBrowser.Api/Playback/TranscodingThrottler.cs index 19a9fe9343..58cfa086e3 100644 --- a/MediaBrowser.Api/Playback/TranscodingThrottler.cs +++ b/MediaBrowser.Api/Playback/TranscodingThrottler.cs @@ -1,4 +1,6 @@ -using MediaBrowser.Model.Logging; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Logging; using System; using System.IO; using System.Threading; @@ -11,13 +13,18 @@ namespace MediaBrowser.Api.Playback private readonly ILogger _logger; private Timer _timer; private bool _isPaused; + private readonly IConfigurationManager _config; - private readonly long _gapLengthInTicks = TimeSpan.FromMinutes(2).Ticks; - - public TranscodingThrottler(TranscodingJob job, ILogger logger) + public TranscodingThrottler(TranscodingJob job, ILogger logger, IConfigurationManager config) { _job = job; _logger = logger; + _config = config; + } + + private EncodingOptions GetOptions() + { + return _config.GetConfiguration("encoding"); } public void Start() @@ -33,7 +40,9 @@ namespace MediaBrowser.Api.Playback return; } - if (IsThrottleAllowed(_job)) + var options = GetOptions(); + + if (options.EnableThrottling && IsThrottleAllowed(_job, options.ThrottleThresholdSeconds)) { PauseTranscoding(); } @@ -79,19 +88,20 @@ namespace MediaBrowser.Api.Playback } } - private bool IsThrottleAllowed(TranscodingJob job) + private bool IsThrottleAllowed(TranscodingJob job, int thresholdSeconds) { var bytesDownloaded = job.BytesDownloaded ?? 0; var transcodingPositionTicks = job.TranscodingPositionTicks ?? 0; var downloadPositionTicks = job.DownloadPositionTicks ?? 0; var path = job.Path; + var gapLengthInTicks = TimeSpan.FromSeconds(thresholdSeconds).Ticks; if (downloadPositionTicks > 0 && transcodingPositionTicks > 0) { // HLS - time-based consideration - var targetGap = _gapLengthInTicks; + var targetGap = gapLengthInTicks; var gap = transcodingPositionTicks - downloadPositionTicks; if (gap < targetGap) @@ -113,7 +123,7 @@ namespace MediaBrowser.Api.Playback var bytesTranscoded = job.BytesTranscoded ?? new FileInfo(path).Length; // Estimate the bytes the transcoder should be ahead - double gapFactor = _gapLengthInTicks; + double gapFactor = gapLengthInTicks; gapFactor /= transcodingPositionTicks; var targetGap = bytesTranscoded * gapFactor; diff --git a/MediaBrowser.Dlna/Didl/DidlBuilder.cs b/MediaBrowser.Dlna/Didl/DidlBuilder.cs index 469b60a9c2..7f696f300b 100644 --- a/MediaBrowser.Dlna/Didl/DidlBuilder.cs +++ b/MediaBrowser.Dlna/Didl/DidlBuilder.cs @@ -158,7 +158,9 @@ namespace MediaBrowser.Dlna.Didl streamInfo.TranscodeSeekInfo, streamInfo.IsTargetAnamorphic, streamInfo.IsTargetCabac, - streamInfo.TargetRefFrames); + streamInfo.TargetRefFrames, + streamInfo.TargetVideoStreamCount, + streamInfo.TargetAudioStreamCount); foreach (var contentFeature in contentFeatureList) { @@ -280,7 +282,9 @@ namespace MediaBrowser.Dlna.Didl streamInfo.TargetTimestamp, streamInfo.IsTargetAnamorphic, streamInfo.IsTargetCabac, - streamInfo.TargetRefFrames); + streamInfo.TargetRefFrames, + streamInfo.TargetVideoStreamCount, + streamInfo.TargetAudioStreamCount); var filename = url.Substring(0, url.IndexOf('?')); diff --git a/MediaBrowser.Dlna/PlayTo/PlayToController.cs b/MediaBrowser.Dlna/PlayTo/PlayToController.cs index 17385bda62..3eb091a194 100644 --- a/MediaBrowser.Dlna/PlayTo/PlayToController.cs +++ b/MediaBrowser.Dlna/PlayTo/PlayToController.cs @@ -526,7 +526,9 @@ namespace MediaBrowser.Dlna.PlayTo streamInfo.TranscodeSeekInfo, streamInfo.IsTargetAnamorphic, streamInfo.IsTargetCabac, - streamInfo.TargetRefFrames); + streamInfo.TargetRefFrames, + streamInfo.TargetVideoStreamCount, + streamInfo.TargetAudioStreamCount); return list.FirstOrDefault(); } diff --git a/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs index a6a87a3fcb..e9204ef5b0 100644 --- a/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.MediaEncoding.Encoder { public class AudioEncoder : BaseEncoder { - public AudioEncoder(MediaEncoder mediaEncoder, ILogger logger, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ILiveTvManager liveTvManager, IIsoManager isoManager, ILibraryManager libraryManager, IChannelManager channelManager, ISessionManager sessionManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager) : base(mediaEncoder, logger, configurationManager, fileSystem, liveTvManager, isoManager, libraryManager, channelManager, sessionManager, subtitleEncoder, mediaSourceManager) + public AudioEncoder(MediaEncoder mediaEncoder, ILogger logger, IServerConfigurationManager configurationManager, IFileSystem fileSystem, IIsoManager isoManager, ILibraryManager libraryManager, ISessionManager sessionManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager) : base(mediaEncoder, logger, configurationManager, fileSystem, isoManager, libraryManager, sessionManager, subtitleEncoder, mediaSourceManager) { } diff --git a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs index 44e0d15173..8266f4d3ad 100644 --- a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs @@ -1,6 +1,5 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Common.IO; -using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; @@ -14,6 +13,7 @@ using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; +using MediaBrowser.Model.MediaInfo; using System; using System.Collections.Generic; using System.Diagnostics; @@ -31,10 +31,8 @@ namespace MediaBrowser.MediaEncoding.Encoder protected readonly ILogger Logger; protected readonly IServerConfigurationManager ConfigurationManager; protected readonly IFileSystem FileSystem; - protected readonly ILiveTvManager LiveTvManager; protected readonly IIsoManager IsoManager; protected readonly ILibraryManager LibraryManager; - protected readonly IChannelManager ChannelManager; protected readonly ISessionManager SessionManager; protected readonly ISubtitleEncoder SubtitleEncoder; protected readonly IMediaSourceManager MediaSourceManager; @@ -45,20 +43,18 @@ namespace MediaBrowser.MediaEncoding.Encoder ILogger logger, IServerConfigurationManager configurationManager, IFileSystem fileSystem, - ILiveTvManager liveTvManager, IIsoManager isoManager, ILibraryManager libraryManager, - IChannelManager channelManager, - ISessionManager sessionManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager) + ISessionManager sessionManager, + ISubtitleEncoder subtitleEncoder, + IMediaSourceManager mediaSourceManager) { MediaEncoder = mediaEncoder; Logger = logger; ConfigurationManager = configurationManager; FileSystem = fileSystem; - LiveTvManager = liveTvManager; IsoManager = isoManager; LibraryManager = libraryManager; - ChannelManager = channelManager; SessionManager = sessionManager; SubtitleEncoder = subtitleEncoder; MediaSourceManager = mediaSourceManager; @@ -68,7 +64,7 @@ namespace MediaBrowser.MediaEncoding.Encoder IProgress progress, CancellationToken cancellationToken) { - var encodingJob = await new EncodingJobFactory(Logger, LiveTvManager, LibraryManager, ChannelManager, MediaSourceManager) + var encodingJob = await new EncodingJobFactory(Logger, LibraryManager, MediaSourceManager) .CreateJob(options, IsVideoEncoder, progress, cancellationToken).ConfigureAwait(false); encodingJob.OutputFilePath = GetOutputFilePath(encodingJob); @@ -477,53 +473,25 @@ namespace MediaBrowser.MediaEncoding.Encoder state.IsoMount = await IsoManager.Mount(state.MediaPath, cancellationToken).ConfigureAwait(false); } - if (string.IsNullOrEmpty(state.MediaPath)) + if (state.MediaSource.RequiresOpening) { - var checkCodecs = false; - - if (string.Equals(state.ItemType, typeof(LiveTvChannel).Name)) + var liveStreamResponse = await MediaSourceManager.OpenLiveStream(new LiveStreamRequest { - var streamInfo = await LiveTvManager.GetChannelStream(state.Options.ItemId, cancellationToken).ConfigureAwait(false); - - state.LiveTvStreamId = streamInfo.Id; - - state.MediaPath = streamInfo.Path; - state.InputProtocol = streamInfo.Protocol; + OpenToken = state.MediaSource.OpenToken - await Task.Delay(1500, cancellationToken).ConfigureAwait(false); + }, false, cancellationToken).ConfigureAwait(false); - AttachMediaStreamInfo(state, streamInfo, state.Options); - checkCodecs = true; - } + AttachMediaStreamInfo(state, liveStreamResponse.MediaSource, state.Options); - else if (string.Equals(state.ItemType, typeof(LiveTvVideoRecording).Name) || - string.Equals(state.ItemType, typeof(LiveTvAudioRecording).Name)) + if (state.IsVideoRequest) { - var streamInfo = await LiveTvManager.GetRecordingStream(state.Options.ItemId, cancellationToken).ConfigureAwait(false); - - state.LiveTvStreamId = streamInfo.Id; - - state.MediaPath = streamInfo.Path; - state.InputProtocol = streamInfo.Protocol; - - await Task.Delay(1500, cancellationToken).ConfigureAwait(false); - - AttachMediaStreamInfo(state, streamInfo, state.Options); - checkCodecs = true; + EncodingJobFactory.TryStreamCopy(state, state.Options); } + } - if (state.IsVideoRequest && checkCodecs) - { - if (state.VideoStream != null && EncodingJobFactory.CanStreamCopyVideo(state.Options, state.VideoStream)) - { - state.OutputVideoCodec = "copy"; - } - - if (state.AudioStream != null && EncodingJobFactory.CanStreamCopyAudio(state.Options, state.AudioStream, state.SupportedAudioCodecs)) - { - state.OutputAudioCodec = "copy"; - } - } + if (state.MediaSource.BufferMs.HasValue) + { + await Task.Delay(state.MediaSource.BufferMs.Value, cancellationToken).ConfigureAwait(false); } } @@ -531,22 +499,7 @@ namespace MediaBrowser.MediaEncoding.Encoder MediaSourceInfo mediaSource, EncodingJobOptions videoRequest) { - state.InputProtocol = mediaSource.Protocol; - state.MediaPath = mediaSource.Path; - state.RunTimeTicks = mediaSource.RunTimeTicks; - state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders; - state.InputBitrate = mediaSource.Bitrate; - state.InputFileSize = mediaSource.Size; - state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate; - - if (state.ReadInputAtNativeFramerate) - { - state.OutputAudioSync = "1000"; - state.InputVideoSync = "-1"; - state.InputAudioSync = "1"; - } - - EncodingJobFactory.AttachMediaStreamInfo(state, mediaSource.MediaStreams, videoRequest); + EncodingJobFactory.AttachMediaStreamInfo(state, mediaSource, videoRequest); } /// diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs index c8d121eead..767f3f829b 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs @@ -1,7 +1,8 @@ -using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Drawing; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; @@ -26,7 +27,7 @@ namespace MediaBrowser.MediaEncoding.Encoder public EncodingJobOptions Options { get; set; } public string InputContainer { get; set; } - public List AllMediaStreams { get; set; } + public MediaSourceInfo MediaSource { get; set; } public MediaStream AudioStream { get; set; } public MediaStream VideoStream { get; set; } public MediaStream SubtitleStream { get; set; } @@ -76,12 +77,12 @@ namespace MediaBrowser.MediaEncoding.Encoder } private readonly ILogger _logger; - private readonly ILiveTvManager _liveTvManager; + private readonly IMediaSourceManager _mediaSourceManager; - public EncodingJob(ILogger logger, ILiveTvManager liveTvManager) + public EncodingJob(ILogger logger, IMediaSourceManager mediaSourceManager) { _logger = logger; - _liveTvManager = liveTvManager; + _mediaSourceManager = mediaSourceManager; Id = Guid.NewGuid().ToString("N"); RemoteHttpHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); @@ -89,7 +90,6 @@ namespace MediaBrowser.MediaEncoding.Encoder SupportedAudioCodecs = new List(); PlayableStreamFileNames = new List(); RemoteHttpHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); - AllMediaStreams = new List(); TaskCompletionSource = new TaskCompletionSource(); } @@ -136,15 +136,15 @@ namespace MediaBrowser.MediaEncoding.Encoder private async void DisposeLiveStream() { - if (!string.IsNullOrEmpty(LiveTvStreamId)) + if (MediaSource.RequiresClosing) { try { - await _liveTvManager.CloseLiveStream(LiveTvStreamId, CancellationToken.None).ConfigureAwait(false); + await _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId, CancellationToken.None).ConfigureAwait(false); } catch (Exception ex) { - _logger.ErrorException("Error closing live tv stream", ex); + _logger.ErrorException("Error closing media source", ex); } } } @@ -394,6 +394,42 @@ namespace MediaBrowser.MediaEncoding.Encoder } } + public int? TargetVideoStreamCount + { + get + { + if (Options.Static) + { + return GetMediaStreamCount(MediaStreamType.Video, int.MaxValue); + } + return GetMediaStreamCount(MediaStreamType.Video, 1); + } + } + + public int? TargetAudioStreamCount + { + get + { + if (Options.Static) + { + return GetMediaStreamCount(MediaStreamType.Audio, int.MaxValue); + } + return GetMediaStreamCount(MediaStreamType.Audio, 1); + } + } + + private int? GetMediaStreamCount(MediaStreamType type, int limit) + { + var count = MediaSource.GetStreamCount(type); + + if (count.HasValue) + { + count = Math.Min(count.Value, limit); + } + + return count; + } + public void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded) { var ticks = transcodingPosition.HasValue ? transcodingPosition.Value.Ticks : (long?)null; diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs index c5783e1882..8d82010749 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs @@ -1,9 +1,10 @@ -using MediaBrowser.Controller.Channels; +using System.IO; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using MediaBrowser.Model.MediaInfo; @@ -19,19 +20,15 @@ namespace MediaBrowser.MediaEncoding.Encoder public class EncodingJobFactory { private readonly ILogger _logger; - private readonly ILiveTvManager _liveTvManager; private readonly ILibraryManager _libraryManager; - private readonly IChannelManager _channelManager; private readonly IMediaSourceManager _mediaSourceManager; protected static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - public EncodingJobFactory(ILogger logger, ILiveTvManager liveTvManager, ILibraryManager libraryManager, IChannelManager channelManager, IMediaSourceManager mediaSourceManager) + public EncodingJobFactory(ILogger logger, ILibraryManager libraryManager, IMediaSourceManager mediaSourceManager) { _logger = logger; - _liveTvManager = liveTvManager; _libraryManager = libraryManager; - _channelManager = channelManager; _mediaSourceManager = mediaSourceManager; } @@ -42,9 +39,9 @@ namespace MediaBrowser.MediaEncoding.Encoder if (string.IsNullOrEmpty(request.AudioCodec)) { request.AudioCodec = InferAudioCodec(request.OutputContainer); - } - - var state = new EncodingJob(_logger, _liveTvManager) + } + + var state = new EncodingJob(_logger, _mediaSourceManager) { Options = options, IsVideoRequest = isVideoRequest, @@ -58,106 +55,17 @@ namespace MediaBrowser.MediaEncoding.Encoder } var item = _libraryManager.GetItemById(request.ItemId); - - List mediaStreams = null; - state.ItemType = item.GetType().Name; - if (item is ILiveTvRecording) - { - var recording = await _liveTvManager.GetInternalRecording(request.ItemId, cancellationToken).ConfigureAwait(false); - - state.VideoType = VideoType.VideoFile; - state.IsInputVideo = string.Equals(recording.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase); - - var path = recording.RecordingInfo.Path; - var mediaUrl = recording.RecordingInfo.Url; - - var source = string.IsNullOrEmpty(request.MediaSourceId) - ? recording.GetMediaSources(false).First() - : _mediaSourceManager.GetStaticMediaSource(recording, request.MediaSourceId, false); - - mediaStreams = source.MediaStreams; - - // Just to prevent this from being null and causing other methods to fail - state.MediaPath = string.Empty; - - if (!string.IsNullOrEmpty(path)) - { - state.MediaPath = path; - state.InputProtocol = MediaProtocol.File; - } - else if (!string.IsNullOrEmpty(mediaUrl)) - { - state.MediaPath = mediaUrl; - state.InputProtocol = MediaProtocol.Http; - } - - state.RunTimeTicks = recording.RunTimeTicks; - state.DeInterlace = true; - state.OutputAudioSync = "1000"; - state.InputVideoSync = "-1"; - state.InputAudioSync = "1"; - state.InputContainer = recording.Container; - state.ReadInputAtNativeFramerate = source.ReadAtNativeFramerate; - } - else if (item is LiveTvChannel) - { - var channel = _liveTvManager.GetInternalChannel(request.ItemId); - - state.VideoType = VideoType.VideoFile; - state.IsInputVideo = string.Equals(channel.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase); - mediaStreams = new List(); - - state.DeInterlace = true; - - // Just to prevent this from being null and causing other methods to fail - state.MediaPath = string.Empty; - } - else - { - var mediaSources = await _mediaSourceManager.GetPlayackMediaSources(request.ItemId, false, cancellationToken).ConfigureAwait(false); + state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase); - var mediaSource = string.IsNullOrEmpty(request.MediaSourceId) - ? mediaSources.First() - : mediaSources.First(i => string.Equals(i.Id, request.MediaSourceId)); - - mediaStreams = mediaSource.MediaStreams; - - state.MediaPath = mediaSource.Path; - state.InputProtocol = mediaSource.Protocol; - state.InputContainer = mediaSource.Container; - state.InputFileSize = mediaSource.Size; - state.InputBitrate = mediaSource.Bitrate; - state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate; - state.RunTimeTicks = mediaSource.RunTimeTicks; - state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders; - - var video = item as Video; - - if (video != null) - { - state.IsInputVideo = true; - - if (mediaSource.VideoType.HasValue) - { - state.VideoType = mediaSource.VideoType.Value; - } + var mediaSources = await _mediaSourceManager.GetPlayackMediaSources(request.ItemId, false, cancellationToken).ConfigureAwait(false); - state.IsoType = mediaSource.IsoType; + var mediaSource = string.IsNullOrEmpty(request.MediaSourceId) + ? mediaSources.First() + : mediaSources.First(i => string.Equals(i.Id, request.MediaSourceId)); - state.PlayableStreamFileNames = mediaSource.PlayableStreamFileNames.ToList(); - - if (mediaSource.Timestamp.HasValue) - { - state.InputTimestamp = mediaSource.Timestamp.Value; - } - } - - state.RunTimeTicks = mediaSource.RunTimeTicks; - } - - AttachMediaStreamInfo(state, mediaStreams, request); + AttachMediaStreamInfo(state, mediaSource, options); state.OutputAudioBitrate = GetAudioBitrateParam(request, state.AudioStream); state.OutputAudioSampleRate = request.AudioSampleRate; @@ -185,26 +93,73 @@ namespace MediaBrowser.MediaEncoding.Encoder ApplyDeviceProfileSettings(state); - if (isVideoRequest) + TryStreamCopy(state, request); + + return state; + } + + internal static void TryStreamCopy(EncodingJob state, + EncodingJobOptions videoRequest) + { + if (state.IsVideoRequest) { - if (state.VideoStream != null && CanStreamCopyVideo(request, state.VideoStream)) + if (state.VideoStream != null && CanStreamCopyVideo(videoRequest, state.VideoStream)) { state.OutputVideoCodec = "copy"; } - if (state.AudioStream != null && CanStreamCopyAudio(request, state.AudioStream, state.SupportedAudioCodecs)) + if (state.AudioStream != null && CanStreamCopyAudio(videoRequest, state.AudioStream, state.SupportedAudioCodecs)) { state.OutputAudioCodec = "copy"; } } - - return state; } internal static void AttachMediaStreamInfo(EncodingJob state, - List mediaStreams, + MediaSourceInfo mediaSource, EncodingJobOptions videoRequest) { + state.MediaPath = mediaSource.Path; + state.InputProtocol = mediaSource.Protocol; + state.InputContainer = mediaSource.Container; + state.InputFileSize = mediaSource.Size; + state.InputBitrate = mediaSource.Bitrate; + state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate; + state.RunTimeTicks = mediaSource.RunTimeTicks; + state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders; + + if (mediaSource.VideoType.HasValue) + { + state.VideoType = mediaSource.VideoType.Value; + } + + state.IsoType = mediaSource.IsoType; + + state.PlayableStreamFileNames = mediaSource.PlayableStreamFileNames.ToList(); + + if (mediaSource.Timestamp.HasValue) + { + state.InputTimestamp = mediaSource.Timestamp.Value; + } + + state.InputProtocol = mediaSource.Protocol; + state.MediaPath = mediaSource.Path; + state.RunTimeTicks = mediaSource.RunTimeTicks; + state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders; + state.InputBitrate = mediaSource.Bitrate; + state.InputFileSize = mediaSource.Size; + state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate; + + if (state.ReadInputAtNativeFramerate || + mediaSource.Protocol == MediaProtocol.File && string.Equals(mediaSource.Container, "wtv", StringComparison.OrdinalIgnoreCase)) + { + state.OutputAudioSync = "1000"; + state.InputVideoSync = "-1"; + state.InputAudioSync = "1"; + } + + var mediaStreams = mediaSource.MediaStreams; + if (videoRequest != null) { if (string.IsNullOrEmpty(videoRequest.VideoCodec)) @@ -233,7 +188,7 @@ namespace MediaBrowser.MediaEncoding.Encoder state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true); } - state.AllMediaStreams = mediaStreams; + state.MediaSource = mediaSource; } /// @@ -771,7 +726,9 @@ namespace MediaBrowser.MediaEncoding.Encoder state.TargetTimestamp, state.IsTargetAnamorphic, state.IsTargetCabac, - state.TargetRefFrames); + state.TargetRefFrames, + state.TargetVideoStreamCount, + state.TargetAudioStreamCount); if (mediaProfile != null) { diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 7fd91bf6f8..4258898073 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -577,10 +577,8 @@ namespace MediaBrowser.MediaEncoding.Encoder _logger, ConfigurationManager, FileSystem, - LiveTvManager, IsoManager, LibraryManager, - ChannelManager, SessionManager, SubtitleEncoder(), MediaSourceManager()) @@ -599,10 +597,8 @@ namespace MediaBrowser.MediaEncoding.Encoder _logger, ConfigurationManager, FileSystem, - LiveTvManager, IsoManager, LibraryManager, - ChannelManager, SessionManager, SubtitleEncoder(), MediaSourceManager()) diff --git a/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs index b8ed97b1fc..26d4a76509 100644 --- a/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs @@ -15,7 +15,7 @@ namespace MediaBrowser.MediaEncoding.Encoder { public class VideoEncoder : BaseEncoder { - public VideoEncoder(MediaEncoder mediaEncoder, ILogger logger, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ILiveTvManager liveTvManager, IIsoManager isoManager, ILibraryManager libraryManager, IChannelManager channelManager, ISessionManager sessionManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager) : base(mediaEncoder, logger, configurationManager, fileSystem, liveTvManager, isoManager, libraryManager, channelManager, sessionManager, subtitleEncoder, mediaSourceManager) + public VideoEncoder(MediaEncoder mediaEncoder, ILogger logger, IServerConfigurationManager configurationManager, IFileSystem fileSystem, IIsoManager isoManager, ILibraryManager libraryManager, ISessionManager sessionManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager) : base(mediaEncoder, logger, configurationManager, fileSystem, isoManager, libraryManager, sessionManager, subtitleEncoder, mediaSourceManager) { } diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs index f24367298e..afd67eb153 100644 --- a/MediaBrowser.Model/Configuration/EncodingOptions.cs +++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs @@ -8,12 +8,16 @@ namespace MediaBrowser.Model.Configuration public double DownMixAudioBoost { get; set; } public string H264Encoder { get; set; } public bool EnableDebugLogging { get; set; } + public bool EnableThrottling { get; set; } + public int ThrottleThresholdSeconds { get; set; } public EncodingOptions() { H264Encoder = "libx264"; DownMixAudioBoost = 2; EncodingQuality = EncodingQuality.Auto; + EnableThrottling = true; + ThrottleThresholdSeconds = 120; } } } diff --git a/MediaBrowser.Model/Dlna/ConditionProcessor.cs b/MediaBrowser.Model/Dlna/ConditionProcessor.cs index e0a8e239e1..3769634440 100644 --- a/MediaBrowser.Model/Dlna/ConditionProcessor.cs +++ b/MediaBrowser.Model/Dlna/ConditionProcessor.cs @@ -20,7 +20,9 @@ namespace MediaBrowser.Model.Dlna TransportStreamTimestamp? timestamp, bool? isAnamorphic, bool? isCabac, - int? refFrames) + int? refFrames, + int? numVideoStreams, + int? numAudioStreams) { switch (condition.Property) { @@ -56,6 +58,10 @@ namespace MediaBrowser.Model.Dlna return IsConditionSatisfied(condition, width); case ProfileConditionValue.RefFrames: return IsConditionSatisfied(condition, refFrames); + case ProfileConditionValue.NumAudioStreams: + return IsConditionSatisfied(condition, numAudioStreams); + case ProfileConditionValue.NumVideoStreams: + return IsConditionSatisfied(condition, numVideoStreams); case ProfileConditionValue.VideoTimestamp: return IsConditionSatisfied(condition, timestamp); default: @@ -92,7 +98,8 @@ namespace MediaBrowser.Model.Dlna public bool IsVideoAudioConditionSatisfied(ProfileCondition condition, int? audioChannels, int? audioBitrate, - string audioProfile) + string audioProfile, + bool? isSecondaryTrack) { switch (condition.Property) { @@ -102,6 +109,8 @@ namespace MediaBrowser.Model.Dlna return IsConditionSatisfied(condition, audioBitrate); case ProfileConditionValue.AudioChannels: return IsConditionSatisfied(condition, audioChannels); + case ProfileConditionValue.IsSecondaryAudio: + return IsConditionSatisfied(condition, isSecondaryTrack); default: throw new ArgumentException("Unexpected condition on audio file: " + condition.Property); } diff --git a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs index a3eeecff2f..8161f1c268 100644 --- a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs +++ b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs @@ -117,7 +117,9 @@ namespace MediaBrowser.Model.Dlna TranscodeSeekInfo transcodeSeekInfo, bool? isAnamorphic, bool? isCabac, - int? refFrames) + int? refFrames, + int? numVideoStreams, + int? numAudioStreams) { // 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 string orgOp = ";DLNA.ORG_OP=" + DlnaMaps.GetOrgOpValue(runtimeTicks.HasValue, isDirectStream, transcodeSeekInfo); @@ -158,7 +160,9 @@ namespace MediaBrowser.Model.Dlna timestamp, isAnamorphic, isCabac, - refFrames); + refFrames, + numVideoStreams, + numAudioStreams); List orgPnValues = new List(); diff --git a/MediaBrowser.Model/Dlna/DeviceProfile.cs b/MediaBrowser.Model/Dlna/DeviceProfile.cs index 4b137a268c..8b9b2edf36 100644 --- a/MediaBrowser.Model/Dlna/DeviceProfile.cs +++ b/MediaBrowser.Model/Dlna/DeviceProfile.cs @@ -281,7 +281,9 @@ namespace MediaBrowser.Model.Dlna TransportStreamTimestamp timestamp, bool? isAnamorphic, bool? isCabac, - int? refFrames) + int? refFrames, + int? numVideoStreams, + int? numAudioStreams) { container = StringHelper.TrimStart((container ?? string.Empty), '.'); @@ -315,7 +317,7 @@ namespace MediaBrowser.Model.Dlna var anyOff = false; foreach (ProfileCondition c in i.Conditions) { - if (!conditionProcessor.IsVideoConditionSatisfied(c, audioBitrate, audioChannels, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isCabac, refFrames)) + if (!conditionProcessor.IsVideoConditionSatisfied(c, audioBitrate, audioChannels, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isCabac, refFrames, numVideoStreams, numAudioStreams)) { anyOff = true; break; diff --git a/MediaBrowser.Model/Dlna/ProfileConditionValue.cs b/MediaBrowser.Model/Dlna/ProfileConditionValue.cs index ae6dc74c8c..7563ffb5af 100644 --- a/MediaBrowser.Model/Dlna/ProfileConditionValue.cs +++ b/MediaBrowser.Model/Dlna/ProfileConditionValue.cs @@ -17,6 +17,9 @@ VideoTimestamp = 12, IsAnamorphic = 13, RefFrames = 14, - IsCabac = 15 + IsCabac = 15, + NumAudioStreams = 16, + NumVideoStreams = 17, + IsSecondaryAudio } } \ No newline at end of file diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 6534eda10c..1cc37de57c 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -495,10 +495,13 @@ namespace MediaBrowser.Model.Dlna int? packetLength = videoStream == null ? null : videoStream.PacketLength; int? refFrames = videoStream == null ? null : videoStream.RefFrames; + int? numAudioStreams = mediaSource.GetStreamCount(MediaStreamType.Audio); + int? numVideoStreams = mediaSource.GetStreamCount(MediaStreamType.Video); + // Check container conditions foreach (ProfileCondition i in conditions) { - if (!conditionProcessor.IsVideoConditionSatisfied(i, audioBitrate, audioChannels, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isCabac, refFrames)) + if (!conditionProcessor.IsVideoConditionSatisfied(i, audioBitrate, audioChannels, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isCabac, refFrames, numVideoStreams, numAudioStreams)) { return null; } @@ -525,7 +528,7 @@ namespace MediaBrowser.Model.Dlna foreach (ProfileCondition i in conditions) { - if (!conditionProcessor.IsVideoConditionSatisfied(i, audioBitrate, audioChannels, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isCabac, refFrames)) + if (!conditionProcessor.IsVideoConditionSatisfied(i, audioBitrate, audioChannels, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isCabac, refFrames, numVideoStreams, numAudioStreams)) { return null; } @@ -554,7 +557,8 @@ namespace MediaBrowser.Model.Dlna foreach (ProfileCondition i in conditions) { - if (!conditionProcessor.IsVideoAudioConditionSatisfied(i, audioChannels, audioBitrate, audioProfile)) + bool? isSecondaryAudio = audioStream == null ? null : mediaSource.IsSecondaryAudio(audioStream); + if (!conditionProcessor.IsVideoAudioConditionSatisfied(i, audioChannels, audioBitrate, audioProfile, isSecondaryAudio)) { return null; } @@ -752,6 +756,9 @@ namespace MediaBrowser.Model.Dlna case ProfileConditionValue.AudioProfile: case ProfileConditionValue.Has64BitOffsets: case ProfileConditionValue.PacketLength: + case ProfileConditionValue.NumAudioStreams: + case ProfileConditionValue.NumVideoStreams: + case ProfileConditionValue.IsSecondaryAudio: case ProfileConditionValue.VideoTimestamp: { // Not supported yet diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index 12319a1227..feee2d765c 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -672,6 +672,42 @@ namespace MediaBrowser.Model.Dlna } } + public int? TargetVideoStreamCount + { + get + { + if (IsDirectStream) + { + return GetMediaStreamCount(MediaStreamType.Video, int.MaxValue); + } + return GetMediaStreamCount(MediaStreamType.Video, 1); + } + } + + public int? TargetAudioStreamCount + { + get + { + if (IsDirectStream) + { + return GetMediaStreamCount(MediaStreamType.Audio, int.MaxValue); + } + return GetMediaStreamCount(MediaStreamType.Audio, 1); + } + } + + private int? GetMediaStreamCount(MediaStreamType type, int limit) + { + var count = MediaSource.GetStreamCount(type); + + if (count.HasValue) + { + count = Math.Min(count.Value, limit); + } + + return count; + } + public List GetSelectableAudioStreams() { return GetSelectableStreams(MediaStreamType.Audio); diff --git a/MediaBrowser.Model/Dto/MediaSourceInfo.cs b/MediaBrowser.Model/Dto/MediaSourceInfo.cs index 3b45137241..302d18bc0f 100644 --- a/MediaBrowser.Model/Dto/MediaSourceInfo.cs +++ b/MediaBrowser.Model/Dto/MediaSourceInfo.cs @@ -46,8 +46,8 @@ namespace MediaBrowser.Model.Dto public int? Bitrate { get; set; } public TransportStreamTimestamp? Timestamp { get; set; } - public Dictionary RequiredHttpHeaders { get; set; } - + public Dictionary RequiredHttpHeaders { get; set; } + public string TranscodingUrl { get; set; } public string TranscodingSubProtocol { get; set; } public string TranscodingContainer { get; set; } @@ -135,5 +135,35 @@ namespace MediaBrowser.Model.Dto return null; } + + public int? GetStreamCount(MediaStreamType type) + { + int numMatches = 0; + int numStreams = 0; + + foreach (MediaStream i in MediaStreams) + { + numStreams++; + if (i.Type == type) + { + numMatches++; + } + } + + return numStreams == 0 ? (int?)null : numMatches; + } + + public bool? IsSecondaryAudio(MediaStream stream) + { + foreach (MediaStream currentStream in MediaStreams) + { + if (currentStream.Type == MediaStreamType.Audio) + { + return currentStream.Index != stream.Index; + } + } + + return null; + } } } diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json index fdcc4ceb66..ae20fafbab 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json @@ -35,6 +35,8 @@ "HeaderConfirmation": "Confirmation", "MessageKeyUpdated": "Thank you. Your supporter key has been updated.", "MessageKeyRemoved": "Thank you. Your supporter key has been removed.", + "TitleLiveTV": "Live TV", + "TitleSync": "Sync", "ErrorLaunchingChromecast": "There was an error launching chromecast. Please ensure your device is connected to your wireless network.", "MessageErrorLoadingSupporterInfo": "There was an error loading supporter information. Please try again later.", "MessageLinkYourSupporterKey": "Link your supporter key with up to {0} Emby Connect members to enjoy free access to the following apps:", @@ -93,7 +95,6 @@ "HeaderWelcomeToProjectWebClient": "Welcome to the Emby Web Client", "ButtonTakeTheTour": "Take the tour", "HeaderWelcomeBack": "Welcome back!", - "TitleSync": "Sync", "TitlePlugins": "Plugins", "ButtonTakeTheTourToSeeWhatsNew": "Take the tour to see what's new", "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.", diff --git a/MediaBrowser.Server.Implementations/Localization/Server/server.json b/MediaBrowser.Server.Implementations/Localization/Server/server.json index 0fc603a083..f52d8a9fe3 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/server.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/server.json @@ -1400,5 +1400,7 @@ "HeaderUpcomingPrograms": "Upcoming Programs", "ButtonMoreItems": "More...", "LabelShowLibraryTileNames": "Show library tile names", - "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page" + "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page", + "OptionEnableTranscodingThrottle": "Enable throttling", + "OptionEnableTranscodingThrottleHelp": "Throttling will automatically adjust transcoding speed in order to minimize server cpu utilization during playback." } diff --git a/MediaBrowser.Server.Implementations/Sync/CloudSyncProfile.cs b/MediaBrowser.Server.Implementations/Sync/CloudSyncProfile.cs index 8d4c40fdc5..c3e8cf9444 100644 --- a/MediaBrowser.Server.Implementations/Sync/CloudSyncProfile.cs +++ b/MediaBrowser.Server.Implementations/Sync/CloudSyncProfile.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Model.Dlna; +using System.Collections.Generic; +using MediaBrowser.Model.Dlna; namespace MediaBrowser.Server.Implementations.Sync { @@ -25,6 +26,9 @@ namespace MediaBrowser.Server.Implementations.Sync mkvAudio += ",dca"; } + var videoProfile = "high|main|baseline|constrained baseline"; + var videoLevel = "41"; + DirectPlayProfiles = new[] { new DirectPlayProfile @@ -48,13 +52,37 @@ namespace MediaBrowser.Server.Implementations.Sync } }; - ContainerProfiles = new ContainerProfile[] { }; + ContainerProfiles = new[] + { + new ContainerProfile + { + Type = DlnaProfileType.Video, + Conditions = new [] + { + new ProfileCondition + { + Condition = ProfileConditionType.NotEquals, + Property = ProfileConditionValue.NumAudioStreams, + Value = "0", + IsRequired = false + }, + new ProfileCondition + { + Condition = ProfileConditionType.EqualsAny, + Property = ProfileConditionValue.NumVideoStreams, + Value = "1", + IsRequired = false + } + } + } + }; - CodecProfiles = new[] + var codecProfiles = new List { new CodecProfile { Type = CodecType.Video, + Codec = "h264", Conditions = new [] { new ProfileCondition @@ -65,23 +93,134 @@ namespace MediaBrowser.Server.Implementations.Sync IsRequired = false }, new ProfileCondition + { + Condition = ProfileConditionType.LessThanEqual, + Property = ProfileConditionValue.Width, + Value = "1920", + IsRequired = true + }, + new ProfileCondition { Condition = ProfileConditionType.LessThanEqual, Property = ProfileConditionValue.Height, Value = "1080", + IsRequired = true + }, + new ProfileCondition + { + Condition = ProfileConditionType.LessThanEqual, + Property = ProfileConditionValue.RefFrames, + Value = "4", + IsRequired = false + }, + new ProfileCondition + { + Condition = ProfileConditionType.LessThanEqual, + Property = ProfileConditionValue.VideoFramerate, + Value = "30", + IsRequired = false + }, + new ProfileCondition + { + Condition = ProfileConditionType.Equals, + Property = ProfileConditionValue.IsAnamorphic, + Value = "false", IsRequired = false }, new ProfileCondition + { + Condition = ProfileConditionType.LessThanEqual, + Property = ProfileConditionValue.VideoLevel, + Value = videoLevel, + IsRequired = false + }, + new ProfileCondition + { + Condition = ProfileConditionType.EqualsAny, + Property = ProfileConditionValue.VideoProfile, + Value = videoProfile, + IsRequired = false + } + } + }, + new CodecProfile + { + Type = CodecType.Video, + Codec = "mpeg4", + Conditions = new [] + { + new ProfileCondition + { + Condition = ProfileConditionType.LessThanEqual, + Property = ProfileConditionValue.VideoBitDepth, + Value = "8", + IsRequired = false + }, + new ProfileCondition + { + Condition = ProfileConditionType.LessThanEqual, + Property = ProfileConditionValue.Width, + Value = "1920", + IsRequired = true + }, + new ProfileCondition + { + Condition = ProfileConditionType.LessThanEqual, + Property = ProfileConditionValue.Height, + Value = "1080", + IsRequired = true + }, + new ProfileCondition { Condition = ProfileConditionType.LessThanEqual, Property = ProfileConditionValue.RefFrames, Value = "4", IsRequired = false + }, + new ProfileCondition + { + Condition = ProfileConditionType.LessThanEqual, + Property = ProfileConditionValue.VideoFramerate, + Value = "30", + IsRequired = false + }, + new ProfileCondition + { + Condition = ProfileConditionType.Equals, + Property = ProfileConditionValue.IsAnamorphic, + Value = "false", + IsRequired = false } } } }; + var maxAudioChannels = supportsAc3 || supportsDca ? "5" : "2"; + codecProfiles.Add(new CodecProfile + { + Type = CodecType.Audio, + Codec = "mpeg4", + Conditions = new[] + { + new ProfileCondition + { + Condition = ProfileConditionType.LessThanEqual, + Property = ProfileConditionValue.AudioChannels, + Value = maxAudioChannels, + IsRequired = false + }, + new ProfileCondition + { + Condition = ProfileConditionType.Equals, + Property = ProfileConditionValue.IsSecondaryAudio, + Value = "false", + IsRequired = false + } + } + }); + + CodecProfiles = codecProfiles.ToArray(); + SubtitleProfiles = new[] { new SubtitleProfile diff --git a/MediaBrowser.WebDashboard/Api/PackageCreator.cs b/MediaBrowser.WebDashboard/Api/PackageCreator.cs index 0a995a8455..8982b5739c 100644 --- a/MediaBrowser.WebDashboard/Api/PackageCreator.cs +++ b/MediaBrowser.WebDashboard/Api/PackageCreator.cs @@ -364,7 +364,7 @@ namespace MediaBrowser.WebDashboard.Api "backdrops.js", "sync.js", "syncjob.js", - "syncservices.js", + "appservices.js", "playlistmanager.js", "mediaplayer.js", diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index 03a704a46b..2725d63ad8 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -151,7 +151,7 @@ PreserveNewest - + PreserveNewest @@ -175,7 +175,7 @@ PreserveNewest - + PreserveNewest diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index dd28aae3ac..40da3899c5 100644 --- a/Nuget/MediaBrowser.Common.Internal.nuspec +++ b/Nuget/MediaBrowser.Common.Internal.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common.Internal - 3.0.608 + 3.0.609 MediaBrowser.Common.Internal Luke ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains common components shared by Emby Theater and Emby Server. Not intended for plugin developer consumption. Copyright © Emby 2013 - + diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index 0124a609de..7aa9d30551 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common - 3.0.608 + 3.0.609 MediaBrowser.Common Emby Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Model.Signed.nuspec b/Nuget/MediaBrowser.Model.Signed.nuspec index 3297b66066..8d65ec4315 100644 --- a/Nuget/MediaBrowser.Model.Signed.nuspec +++ b/Nuget/MediaBrowser.Model.Signed.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Model.Signed - 3.0.608 + 3.0.609 MediaBrowser.Model - Signed Edition Emby Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index 0a749c98b6..9a5264da95 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Server.Core - 3.0.608 + 3.0.609 Media Browser.Server.Core Emby Team ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains core components required to build plugins for Emby Server. Copyright © Emby 2013 - + From 07de09f350ab5ba089a27d94cc10b1a8e6f0a700 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 30 Mar 2015 15:57:37 -0400 Subject: [PATCH 26/52] sync updates --- MediaBrowser.Api/ApiEntryPoint.cs | 2 +- MediaBrowser.Api/Playback/MediaInfoService.cs | 38 +++++++- MediaBrowser.Dlna/Didl/DidlBuilder.cs | 7 +- MediaBrowser.Model/Dlna/StreamInfo.cs | 96 +++++++------------ MediaBrowser.Model/Dlna/SubtitleStreamInfo.cs | 1 + MediaBrowser.Model/Entities/MediaStream.cs | 14 ++- .../Sync/CloudSyncProfile.cs | 5 + .../Sync/SyncJobProcessor.cs | 2 +- .../Sync/SyncManager.cs | 2 +- 9 files changed, 97 insertions(+), 70 deletions(-) diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index 444977e352..08ac5671d5 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/MediaBrowser.Api/ApiEntryPoint.cs @@ -296,7 +296,7 @@ namespace MediaBrowser.Api // TODO: Lower this hls timeout var timerDuration = job.Type == TranscodingJobType.Progressive ? 1000 : - 14400000; + 7200000; if (job.KillTimer == null) { diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs index b833dd7350..6eba195453 100644 --- a/MediaBrowser.Api/Playback/MediaInfoService.cs +++ b/MediaBrowser.Api/Playback/MediaInfoService.cs @@ -272,6 +272,11 @@ namespace MediaBrowser.Api.Playback // Set this back to what it was mediaSource.SupportsDirectStream = supportsDirectStream; + + if (streamInfo != null) + { + SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token); + } } if (mediaSource.SupportsDirectStream) @@ -285,6 +290,11 @@ namespace MediaBrowser.Api.Playback { mediaSource.SupportsDirectStream = false; } + + if (streamInfo != null) + { + SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token); + } } if (mediaSource.SupportsTranscoding) @@ -297,10 +307,36 @@ namespace MediaBrowser.Api.Playback if (streamInfo != null && streamInfo.PlayMethod == PlayMethod.Transcode) { streamInfo.StartPositionTicks = startTimeTicks; - mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).Substring(1); + mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).TrimStart('-'); mediaSource.TranscodingContainer = streamInfo.Container; mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol; } + + if (streamInfo != null) + { + SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token); + } + } + } + + private void SetDeviceSpecificSubtitleInfo(StreamInfo info, MediaSourceInfo mediaSource, string accessToken) + { + var profiles = info.GetSubtitleProfiles(false, "-", accessToken); + + foreach (var profile in profiles) + { + foreach (var stream in mediaSource.MediaStreams) + { + if (stream.Type == MediaStreamType.Subtitle && stream.Index == profile.Index) + { + stream.DeliveryMethod = profile.DeliveryMethod; + + if (profile.DeliveryMethod == SubtitleDeliveryMethod.External) + { + stream.DeliveryUrl = profile.Url.TrimStart('-'); + } + } + } } } diff --git a/MediaBrowser.Dlna/Didl/DidlBuilder.cs b/MediaBrowser.Dlna/Didl/DidlBuilder.cs index 7f696f300b..b364414d10 100644 --- a/MediaBrowser.Dlna/Didl/DidlBuilder.cs +++ b/MediaBrowser.Dlna/Didl/DidlBuilder.cs @@ -167,9 +167,12 @@ namespace MediaBrowser.Dlna.Didl AddVideoResource(container, video, deviceId, filter, contentFeature, streamInfo); } - foreach (var subtitle in streamInfo.GetExternalSubtitles(_serverAddress, _accessToken, false)) + foreach (var subtitle in streamInfo.GetSubtitleProfiles(false, _serverAddress, _accessToken)) { - AddSubtitleElement(container, subtitle); + if (subtitle.DeliveryMethod == SubtitleDeliveryMethod.External) + { + AddSubtitleElement(container, subtitle); + } } } diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index feee2d765c..9bfa684be4 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -51,7 +51,7 @@ namespace MediaBrowser.Model.Dlna public int? MaxVideoBitDepth { get; set; } public int? MaxRefFrames { get; set; } - + public float? MaxFramerate { get; set; } public DeviceProfile DeviceProfile { get; set; } @@ -81,7 +81,8 @@ namespace MediaBrowser.Model.Dlna public bool IsDirectStream { - get { + get + { return PlayMethod == PlayMethod.DirectStream || PlayMethod == PlayMethod.DirectPlay; } @@ -175,7 +176,7 @@ namespace MediaBrowser.Model.Dlna { list.Add(pair.Value); } - + return string.Format("Params={0}", string.Join(";", list.ToArray())); } @@ -199,7 +200,7 @@ namespace MediaBrowser.Model.Dlna list.Add(new NameValuePair("MaxHeight", item.MaxHeight.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxHeight.Value) : string.Empty)); list.Add(new NameValuePair("StartTimeTicks", StringHelper.ToStringCultureInvariant(item.StartPositionTicks))); list.Add(new NameValuePair("Level", item.VideoLevel.HasValue ? StringHelper.ToStringCultureInvariant(item.VideoLevel.Value) : string.Empty)); - + list.Add(new NameValuePair("ClientTime", item.IsDirectStream ? string.Empty : DateTime.UtcNow.Ticks.ToString(CultureInfo.InvariantCulture))); list.Add(new NameValuePair("MaxRefFrames", item.MaxRefFrames.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxRefFrames.Value) : string.Empty)); list.Add(new NameValuePair("MaxVideoBitDepth", item.MaxVideoBitDepth.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxVideoBitDepth.Value) : string.Empty)); @@ -216,53 +217,25 @@ namespace MediaBrowser.Model.Dlna return list; } - public List GetExternalSubtitles(bool includeSelectedTrackOnly) + public List GetExternalSubtitles(bool includeSelectedTrackOnly, string baseUrl, string accessToken) { - List list = new List(); + List list = GetSubtitleProfiles(includeSelectedTrackOnly, baseUrl, accessToken); + List newList = new List(); // First add the selected track - if (SubtitleStreamIndex.HasValue) + foreach (SubtitleStreamInfo stream in list) { - foreach (MediaStream stream in MediaSource.MediaStreams) - { - if (stream.Type == MediaStreamType.Subtitle && stream.Index == SubtitleStreamIndex.Value) - { - SubtitleStreamInfo info = GetSubtitleStreamInfo(stream); - - if (info != null) - { - list.Add(info); - } - } - } - } - - if (!includeSelectedTrackOnly) - { - foreach (MediaStream stream in MediaSource.MediaStreams) + if (stream.DeliveryMethod == SubtitleDeliveryMethod.External) { - if (stream.Type == MediaStreamType.Subtitle && (!SubtitleStreamIndex.HasValue || stream.Index != SubtitleStreamIndex.Value)) - { - SubtitleStreamInfo info = GetSubtitleStreamInfo(stream); - - if (info != null) - { - list.Add(info); - } - } + newList.Add(stream); } } - return list; + return newList; } - public List GetExternalSubtitles(string baseUrl, string accessToken, bool includeSelectedTrackOnly) + public List GetSubtitleProfiles(bool includeSelectedTrackOnly, string baseUrl, string accessToken) { - if (string.IsNullOrEmpty(baseUrl)) - { - throw new ArgumentNullException(baseUrl); - } - List list = new List(); // HLS will preserve timestamps so we can just grab the full subtitle stream @@ -279,10 +252,7 @@ namespace MediaBrowser.Model.Dlna { SubtitleStreamInfo info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks); - if (info != null) - { - list.Add(info); - } + list.Add(info); } } } @@ -295,14 +265,11 @@ namespace MediaBrowser.Model.Dlna { SubtitleStreamInfo info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks); - if (info != null) - { - list.Add(info); - } + list.Add(info); } } } - + return list; } @@ -310,15 +277,22 @@ namespace MediaBrowser.Model.Dlna { SubtitleStreamInfo info = GetSubtitleStreamInfo(stream); - if (info != null) + if (info.DeliveryMethod == SubtitleDeliveryMethod.External) { - info.Url = string.Format("{0}/Videos/{1}/{2}/Subtitles/{3}/{4}/Stream.{5}", - baseUrl, - ItemId, - MediaSourceId, - StringHelper.ToStringCultureInvariant(stream.Index), - StringHelper.ToStringCultureInvariant(startPositionTicks), - SubtitleFormat); + if (MediaSource.Protocol == MediaProtocol.Http) + { + info.Url = stream.Path; + } + else if (!string.IsNullOrEmpty(baseUrl)) + { + info.Url = string.Format("{0}/Videos/{1}/{2}/Subtitles/{3}/{4}/Stream.{5}", + baseUrl, + ItemId, + MediaSourceId, + StringHelper.ToStringCultureInvariant(stream.Index), + StringHelper.ToStringCultureInvariant(startPositionTicks), + SubtitleFormat); + } } return info; @@ -328,18 +302,14 @@ namespace MediaBrowser.Model.Dlna { SubtitleProfile subtitleProfile = StreamBuilder.GetSubtitleProfile(stream, DeviceProfile.SubtitleProfiles, Context); - if (subtitleProfile.Method != SubtitleDeliveryMethod.External) - { - return null; - } - return new SubtitleStreamInfo { IsForced = stream.IsForced, Language = stream.Language, Name = stream.Language ?? "Unknown", Format = SubtitleFormat, - Index = stream.Index + Index = stream.Index, + DeliveryMethod = subtitleProfile.Method }; } diff --git a/MediaBrowser.Model/Dlna/SubtitleStreamInfo.cs b/MediaBrowser.Model/Dlna/SubtitleStreamInfo.cs index a7a8da3ba2..602858ccc9 100644 --- a/MediaBrowser.Model/Dlna/SubtitleStreamInfo.cs +++ b/MediaBrowser.Model/Dlna/SubtitleStreamInfo.cs @@ -8,5 +8,6 @@ namespace MediaBrowser.Model.Dlna public bool IsForced { get; set; } public string Format { get; set; } public int Index { get; set; } + public SubtitleDeliveryMethod DeliveryMethod { get; set; } } } \ No newline at end of file diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index fa075490a5..760829ebff 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Model.Extensions; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Extensions; using System.Diagnostics; namespace MediaBrowser.Model.Entities @@ -135,6 +136,17 @@ namespace MediaBrowser.Model.Entities /// true if this instance is external; otherwise, false. public bool IsExternal { get; set; } + /// + /// Gets or sets the method. + /// + /// The method. + public SubtitleDeliveryMethod? DeliveryMethod { get; set; } + /// + /// Gets or sets the delivery URL. + /// + /// The delivery URL. + public string DeliveryUrl { get; set; } + public bool IsTextSubtitleStream { get diff --git a/MediaBrowser.Server.Implementations/Sync/CloudSyncProfile.cs b/MediaBrowser.Server.Implementations/Sync/CloudSyncProfile.cs index c3e8cf9444..43fb10df00 100644 --- a/MediaBrowser.Server.Implementations/Sync/CloudSyncProfile.cs +++ b/MediaBrowser.Server.Implementations/Sync/CloudSyncProfile.cs @@ -227,6 +227,11 @@ namespace MediaBrowser.Server.Implementations.Sync { Format = "srt", Method = SubtitleDeliveryMethod.External + }, + new SubtitleProfile + { + Format = "vtt", + Method = SubtitleDeliveryMethod.External } }; diff --git a/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs b/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs index b73e0e85f2..7eb015ae7c 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs @@ -495,7 +495,7 @@ namespace MediaBrowser.Server.Implementations.Sync // No sense creating external subs if we're already burning one into the video var externalSubs = streamInfo.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode ? new List() : - streamInfo.GetExternalSubtitles(false); + streamInfo.GetExternalSubtitles(false, null, null); // Mark as requiring conversion if transcoding the video, or if any subtitles need to be extracted var requiresVideoTranscoding = streamInfo.PlayMethod == PlayMethod.Transcode && jobOptions.IsConverting; diff --git a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs index 5e5a0d2fc7..3acc79088d 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs @@ -677,7 +677,6 @@ namespace MediaBrowser.Server.Implementations.Sync syncedItem.Item.MediaSources = new List(); syncedItem.OriginalFileName = Path.GetFileName(libraryItem.Path); - if (string.IsNullOrWhiteSpace(syncedItem.OriginalFileName)) { syncedItem.OriginalFileName = Path.GetFileName(mediaSource.Path); @@ -686,6 +685,7 @@ namespace MediaBrowser.Server.Implementations.Sync // This will be null for items that are not audio/video if (mediaSource != null) { + syncedItem.OriginalFileName = Path.ChangeExtension(syncedItem.OriginalFileName, Path.GetExtension(mediaSource.Path)); syncedItem.Item.MediaSources.Add(mediaSource); } From 90b5ba9222eb076f0d1e92795dcc01dfd3b9cd29 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 30 Mar 2015 15:57:49 -0400 Subject: [PATCH 27/52] update translations --- .../Localization/JavaScript/ar.json | 4 +- .../Localization/JavaScript/bg_BG.json | 4 +- .../Localization/JavaScript/ca.json | 4 +- .../Localization/JavaScript/cs.json | 4 +- .../Localization/JavaScript/da.json | 4 +- .../Localization/JavaScript/de.json | 54 +++---- .../Localization/JavaScript/el.json | 4 +- .../Localization/JavaScript/en_GB.json | 4 +- .../Localization/JavaScript/en_US.json | 4 +- .../Localization/JavaScript/es.json | 4 +- .../Localization/JavaScript/es_MX.json | 48 +++--- .../Localization/JavaScript/fi.json | 4 +- .../Localization/JavaScript/fr.json | 6 +- .../Localization/JavaScript/he.json | 4 +- .../Localization/JavaScript/hr.json | 4 +- .../Localization/JavaScript/hu.json | 4 +- .../Localization/JavaScript/it.json | 4 +- .../Localization/JavaScript/kk.json | 4 +- .../Localization/JavaScript/ms.json | 4 +- .../Localization/JavaScript/nb.json | 4 +- .../Localization/JavaScript/nl.json | 68 ++++----- .../Localization/JavaScript/pl.json | 4 +- .../Localization/JavaScript/pt_BR.json | 22 +-- .../Localization/JavaScript/pt_PT.json | 4 +- .../Localization/JavaScript/ru.json | 26 ++-- .../Localization/JavaScript/sl_SI.json | 4 +- .../Localization/JavaScript/sv.json | 4 +- .../Localization/JavaScript/tr.json | 4 +- .../Localization/JavaScript/uk.json | 4 +- .../Localization/JavaScript/vi.json | 4 +- .../Localization/JavaScript/zh_CN.json | 4 +- .../Localization/JavaScript/zh_TW.json | 4 +- .../Localization/Server/ar.json | 4 +- .../Localization/Server/bg_BG.json | 4 +- .../Localization/Server/ca.json | 4 +- .../Localization/Server/cs.json | 4 +- .../Localization/Server/da.json | 4 +- .../Localization/Server/de.json | 82 ++++++----- .../Localization/Server/el.json | 4 +- .../Localization/Server/en_GB.json | 4 +- .../Localization/Server/en_US.json | 4 +- .../Localization/Server/es.json | 4 +- .../Localization/Server/es_MX.json | 68 ++++----- .../Localization/Server/fi.json | 4 +- .../Localization/Server/fr.json | 10 +- .../Localization/Server/he.json | 4 +- .../Localization/Server/hr.json | 4 +- .../Localization/Server/it.json | 12 +- .../Localization/Server/kk.json | 18 ++- .../Localization/Server/ko.json | 4 +- .../Localization/Server/ms.json | 4 +- .../Localization/Server/nb.json | 4 +- .../Localization/Server/nl.json | 138 +++++++++--------- .../Localization/Server/pl.json | 4 +- .../Localization/Server/pt_BR.json | 16 +- .../Localization/Server/pt_PT.json | 22 +-- .../Localization/Server/ru.json | 34 +++-- .../Localization/Server/sl_SI.json | 4 +- .../Localization/Server/sv.json | 4 +- .../Localization/Server/tr.json | 4 +- .../Localization/Server/uk.json | 4 +- .../Localization/Server/vi.json | 4 +- .../Localization/Server/zh_CN.json | 4 +- .../Localization/Server/zh_TW.json | 4 +- 64 files changed, 474 insertions(+), 346 deletions(-) diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/ar.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/ar.json index 845c0fe268..856a88501e 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/ar.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/ar.json @@ -35,6 +35,8 @@ "HeaderConfirmation": "Confirmation", "MessageKeyUpdated": "Thank you. Your supporter key has been updated.", "MessageKeyRemoved": "Thank you. Your supporter key has been removed.", + "TitleLiveTV": "Live TV", + "TitleSync": "Sync", "ErrorLaunchingChromecast": "There was an error launching chromecast. Please ensure your device is connected to your wireless network.", "MessageErrorLoadingSupporterInfo": "There was an error loading supporter information. Please try again later.", "MessageLinkYourSupporterKey": "Link your supporter key with up to {0} Emby Connect members to enjoy free access to the following apps:", @@ -93,7 +95,6 @@ "HeaderWelcomeToProjectWebClient": "Welcome to the Emby Web Client", "ButtonTakeTheTour": "Take the tour", "HeaderWelcomeBack": "Welcome back!", - "TitleSync": "Sync", "TitlePlugins": "Plugins", "ButtonTakeTheTourToSeeWhatsNew": "Take the tour to see what's new", "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.", @@ -119,6 +120,7 @@ "MessagePlaybackErrorNotAllowed": "You're currently not authorized to play this content. Please contact your system administrator for details.", "MessagePlaybackErrorNoCompatibleStream": "No compatible streams are currently available. Please try again later.", "MessagePlaybackErrorRateLimitExceeded": "Your playback rate limit has been exceeded. Please contact your system administrator for details.", + "MessagePlaybackErrorPlaceHolder": "The content chosen is not playable from this device.", "HeaderSelectAudio": "Select Audio", "HeaderSelectSubtitles": "Select Subtitles", "ButtonMarkForRemoval": "Remove from device", diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/bg_BG.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/bg_BG.json index 35e94c718b..5b036d4d0a 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/bg_BG.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/bg_BG.json @@ -35,6 +35,8 @@ "HeaderConfirmation": "Confirmation", "MessageKeyUpdated": "Thank you. Your supporter key has been updated.", "MessageKeyRemoved": "Thank you. Your supporter key has been removed.", + "TitleLiveTV": "\u0422V \u043d\u0430 \u0436\u0438\u0432\u043e", + "TitleSync": "Sync", "ErrorLaunchingChromecast": "There was an error launching chromecast. Please ensure your device is connected to your wireless network.", "MessageErrorLoadingSupporterInfo": "There was an error loading supporter information. Please try again later.", "MessageLinkYourSupporterKey": "Link your supporter key with up to {0} Emby Connect members to enjoy free access to the following apps:", @@ -93,7 +95,6 @@ "HeaderWelcomeToProjectWebClient": "Welcome to the Emby Web Client", "ButtonTakeTheTour": "\u0420\u0430\u0437\u0433\u043b\u0435\u0434\u0430\u0439 \u043d\u0430\u043e\u043a\u043e\u043b\u043e", "HeaderWelcomeBack": "Welcome back!", - "TitleSync": "Sync", "TitlePlugins": "Plugins", "ButtonTakeTheTourToSeeWhatsNew": "Take the tour to see what's new", "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.", @@ -119,6 +120,7 @@ "MessagePlaybackErrorNotAllowed": "You're currently not authorized to play this content. Please contact your system administrator for details.", "MessagePlaybackErrorNoCompatibleStream": "No compatible streams are currently available. Please try again later.", "MessagePlaybackErrorRateLimitExceeded": "Your playback rate limit has been exceeded. Please contact your system administrator for details.", + "MessagePlaybackErrorPlaceHolder": "The content chosen is not playable from this device.", "HeaderSelectAudio": "Select Audio", "HeaderSelectSubtitles": "Select Subtitles", "ButtonMarkForRemoval": "Remove from device", diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/ca.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/ca.json index 4c6316c919..ecf55bdb0e 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/ca.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/ca.json @@ -35,6 +35,8 @@ "HeaderConfirmation": "Confirmation", "MessageKeyUpdated": "Thank you. Your supporter key has been updated.", "MessageKeyRemoved": "Thank you. Your supporter key has been removed.", + "TitleLiveTV": "Live TV", + "TitleSync": "Sync", "ErrorLaunchingChromecast": "There was an error launching chromecast. Please ensure your device is connected to your wireless network.", "MessageErrorLoadingSupporterInfo": "There was an error loading supporter information. Please try again later.", "MessageLinkYourSupporterKey": "Link your supporter key with up to {0} Emby Connect members to enjoy free access to the following apps:", @@ -93,7 +95,6 @@ "HeaderWelcomeToProjectWebClient": "Welcome to the Emby Web Client", "ButtonTakeTheTour": "Take the tour", "HeaderWelcomeBack": "Welcome back!", - "TitleSync": "Sync", "TitlePlugins": "Plugins", "ButtonTakeTheTourToSeeWhatsNew": "Take the tour to see what's new", "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.", @@ -119,6 +120,7 @@ "MessagePlaybackErrorNotAllowed": "You're currently not authorized to play this content. Please contact your system administrator for details.", "MessagePlaybackErrorNoCompatibleStream": "No compatible streams are currently available. Please try again later.", "MessagePlaybackErrorRateLimitExceeded": "Your playback rate limit has been exceeded. Please contact your system administrator for details.", + "MessagePlaybackErrorPlaceHolder": "The content chosen is not playable from this device.", "HeaderSelectAudio": "Select Audio", "HeaderSelectSubtitles": "Select Subtitles", "ButtonMarkForRemoval": "Remove from device", diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/cs.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/cs.json index cbe2573a63..67b9ca26f6 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/cs.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/cs.json @@ -35,6 +35,8 @@ "HeaderConfirmation": "Potvrzen\u00ed", "MessageKeyUpdated": "Thank you. Your supporter key has been updated.", "MessageKeyRemoved": "Thank you. Your supporter key has been removed.", + "TitleLiveTV": "\u017div\u00e1 TV", + "TitleSync": "Sync", "ErrorLaunchingChromecast": "There was an error launching chromecast. Please ensure your device is connected to your wireless network.", "MessageErrorLoadingSupporterInfo": "There was an error loading supporter information. Please try again later.", "MessageLinkYourSupporterKey": "Link your supporter key with up to {0} Emby Connect members to enjoy free access to the following apps:", @@ -93,7 +95,6 @@ "HeaderWelcomeToProjectWebClient": "Welcome to the Emby Web Client", "ButtonTakeTheTour": "Take the tour", "HeaderWelcomeBack": "Welcome back!", - "TitleSync": "Sync", "TitlePlugins": "Plugins", "ButtonTakeTheTourToSeeWhatsNew": "Take the tour to see what's new", "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.", @@ -119,6 +120,7 @@ "MessagePlaybackErrorNotAllowed": "You're currently not authorized to play this content. Please contact your system administrator for details.", "MessagePlaybackErrorNoCompatibleStream": "No compatible streams are currently available. Please try again later.", "MessagePlaybackErrorRateLimitExceeded": "Your playback rate limit has been exceeded. Please contact your system administrator for details.", + "MessagePlaybackErrorPlaceHolder": "The content chosen is not playable from this device.", "HeaderSelectAudio": "Select Audio", "HeaderSelectSubtitles": "Select Subtitles", "ButtonMarkForRemoval": "Remove from device", diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/da.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/da.json index 7064160f8c..0d4d1bbfa5 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/da.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/da.json @@ -35,6 +35,8 @@ "HeaderConfirmation": "Confirmation", "MessageKeyUpdated": "Thank you. Your supporter key has been updated.", "MessageKeyRemoved": "Thank you. Your supporter key has been removed.", + "TitleLiveTV": "Direkte TV", + "TitleSync": "Sync", "ErrorLaunchingChromecast": "There was an error launching chromecast. Please ensure your device is connected to your wireless network.", "MessageErrorLoadingSupporterInfo": "There was an error loading supporter information. Please try again later.", "MessageLinkYourSupporterKey": "Link your supporter key with up to {0} Emby Connect members to enjoy free access to the following apps:", @@ -93,7 +95,6 @@ "HeaderWelcomeToProjectWebClient": "Welcome to the Emby Web Client", "ButtonTakeTheTour": "Take the tour", "HeaderWelcomeBack": "Welcome back!", - "TitleSync": "Sync", "TitlePlugins": "Plugins", "ButtonTakeTheTourToSeeWhatsNew": "Take the tour to see what's new", "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.", @@ -119,6 +120,7 @@ "MessagePlaybackErrorNotAllowed": "You're currently not authorized to play this content. Please contact your system administrator for details.", "MessagePlaybackErrorNoCompatibleStream": "No compatible streams are currently available. Please try again later.", "MessagePlaybackErrorRateLimitExceeded": "Your playback rate limit has been exceeded. Please contact your system administrator for details.", + "MessagePlaybackErrorPlaceHolder": "The content chosen is not playable from this device.", "HeaderSelectAudio": "Select Audio", "HeaderSelectSubtitles": "Select Subtitles", "ButtonMarkForRemoval": "Remove from device", diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/de.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/de.json index b7bb5c6f03..9431bf07ff 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/de.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/de.json @@ -6,8 +6,8 @@ "Administrator": "Administrator", "Password": "Passwort", "DeleteImage": "Bild l\u00f6schen", - "MessageThankYouForSupporting": "Thank you for supporting Emby.", - "MessagePleaseSupportProject": "Please support Emby.", + "MessageThankYouForSupporting": "Vielen Dank das Sie Emby unterst\u00fctzen.", + "MessagePleaseSupportProject": "Bitte unterst\u00fctzen Sie Emby.", "DeleteImageConfirmation": "M\u00f6chtest du dieses Bild wirklich l\u00f6schen?", "FileReadCancelled": "Dateiimport wurde abgebrochen.", "FileNotFound": "Datei nicht gefunden", @@ -35,9 +35,11 @@ "HeaderConfirmation": "Best\u00e4tigung", "MessageKeyUpdated": "Danke. Dein Unterst\u00fctzerschl\u00fcssel wurde aktualisiert.", "MessageKeyRemoved": "Danke. Ihr Unterst\u00fctzerschl\u00fcssel wurde entfernt.", + "TitleLiveTV": "Live-TV", + "TitleSync": "Synchronisation", "ErrorLaunchingChromecast": "W\u00e4hrend des startens von Chromecast ist ein Fehler aufgetreten. Bitte stelle sicher, dass dein Ger\u00e4te mit dem WLAN verbunden ist.", "MessageErrorLoadingSupporterInfo": "Es trat ein Fehler beim laden der Unterst\u00fctzer-Informationen auf. Bitte versuchen Sie es sp\u00e4ter erneut.", - "MessageLinkYourSupporterKey": "Link your supporter key with up to {0} Emby Connect members to enjoy free access to the following apps:", + "MessageLinkYourSupporterKey": "Verbinden Sie Ihren Unterst\u00fctzer-Schl\u00fcssel mit bis zu {0} Emby Connect Benutzern um kostenfreien Zugriff auf die folgenden Apps zu erhalten:", "HeaderConfirmRemoveUser": "Entferne Benutzer", "MessageSwipeDownOnRemoteControl": "Willkommen zur Fernsteuerung. Streichen Sie irgendwo auf dem Bildschirm nach unten um dorthin zur\u00fcck zu gehen, von wo aus Sie kamen.", "MessageConfirmRemoveConnectSupporter": "M\u00f6chten Sie wirklich zus\u00e4tzliche Unterst\u00fctzer-Features von diesem Anwender entfernen?", @@ -89,11 +91,10 @@ "ConfirmMessageScheduledTaskButton": "Diese Aufgabe l\u00e4uft normalerweise automatisch als geplante Aufgabe. Sie kann jedoch auch manuell von hier gestartet werden. F\u00fcr Einstellungen der geplanten Aufgaben schauen Sie hier:", "HeaderSupporterBenefit": "Eine Unterst\u00fctzer-Mitgliedschaft bietet weitere Funktionen wie z.B. Zugriff auf die Synchronisation, Premium-Plugins, Internet Kan\u00e4le und mehr. {0}Erfahren Sie mehr{1}.", "LabelSyncNoTargetsHelp": "Es sieht so aus als w\u00fcrden Sie aktuell keine Apps verwenden, die Synchronisation unterst\u00fctzen.", - "HeaderWelcomeToProjectServerDashboard": "Welcome to the Emby Server Dashboard", - "HeaderWelcomeToProjectWebClient": "Welcome to the Emby Web Client", + "HeaderWelcomeToProjectServerDashboard": "Willkommen zur Emby Server \u00dcbersicht", + "HeaderWelcomeToProjectWebClient": "Willkommen im Emby Web-Client", "ButtonTakeTheTour": "Mache die Tour", "HeaderWelcomeBack": "Willkommen zur\u00fcck!", - "TitleSync": "Synchronisation", "TitlePlugins": "Plugins", "ButtonTakeTheTourToSeeWhatsNew": "Gehen Sie auf Erkundung und erfahren Sie was neu ist", "MessageNoSyncJobsFound": "Keine Synchronisierungs-Aufgaben gefunden. Um Synchronisierungs-Aufgaben zu erstellen verwenden Sie die dazugeh\u00f6rige Funktion im Web-Interface.", @@ -119,6 +120,7 @@ "MessagePlaybackErrorNotAllowed": "Sie sind nicht befugt diese Inhalte wiederzugeben. Bitte kontaktieren Sie Ihren Systemadministrator f\u00fcr weitere Details.", "MessagePlaybackErrorNoCompatibleStream": "Es sind aktuell keine kompatiblen Streams verf\u00fcgbar. Bitte versuchen Sie es sp\u00e4ter erneut.", "MessagePlaybackErrorRateLimitExceeded": "Ihr Wiedergabelimit wurde \u00fcberschritten. Bitte kontaktieren Sie Ihren Systemadministrator f\u00fcr weitere Details.", + "MessagePlaybackErrorPlaceHolder": "The content chosen is not playable from this device.", "HeaderSelectAudio": "W\u00e4hle Audio", "HeaderSelectSubtitles": "W\u00f6hle Untertitel", "ButtonMarkForRemoval": "Entferne von Ger\u00e4t", @@ -192,7 +194,7 @@ "MessagePleaseSelectOneItem": "Bitte w\u00e4hle mindestens eine Option aus.", "MessagePleaseSelectTwoItems": "Bitte w\u00e4hle mindestens zwei Optionen aus.", "MessageTheFollowingItemsWillBeGrouped": "Die folgenden Titel werden zu einem Element gruppiert:", - "MessageConfirmItemGrouping": "Emby apps will automatically choose the optimal version to play based on device and network performance. Are you sure you wish to continue?", + "MessageConfirmItemGrouping": "Emby Anwendungen w\u00e4hlen automatisch die beste Version anhand des Ger\u00e4tes und der Netzwerkgeschwindigkeit. Sind Sie sich sicher?", "HeaderResume": "Fortsetzen", "HeaderMyViews": "Meine Ansichten", "HeaderLibraryFolders": "Medienverzeichnisse", @@ -240,8 +242,8 @@ "OrganizePatternResult": "Ergebnis: {0}", "HeaderRestart": "Neustart", "HeaderShutdown": "Herunterfahren", - "MessageConfirmRestart": "Are you sure you wish to restart Emby Server?", - "MessageConfirmShutdown": "Are you sure you wish to shutdown Emby Server?", + "MessageConfirmRestart": "M\u00f6chten Sie Emby Server wirklich neu starten?", + "MessageConfirmShutdown": "M\u00f6chsten Sie Emby Server wirklich beenden?", "ButtonUpdateNow": "Jetzt aktualisieren", "ValueItemCount": "{0} Eintrag", "ValueItemCountPlural": "{0} Eintr\u00e4ge", @@ -310,7 +312,7 @@ "OptionBlockLiveTvChannels": "Live-TV Kan\u00e4le", "OptionBlockChannelContent": "Internet Channelinhalte", "ButtonRevoke": "Zur\u00fccknehmen", - "MessageConfirmRevokeApiKey": "Are you sure you wish to revoke this api key? The application's connection to Emby Server will be abruptly terminated.", + "MessageConfirmRevokeApiKey": "M\u00f6chten Sie diesen API Schl\u00fcssel wirklich l\u00f6schen? Die Verbindung der Anwendung zum Emby Server wird sofort unterbrochen.", "HeaderConfirmRevokeApiKey": "Nehme API Schl\u00fcssel zur\u00fcck", "ValueContainer": "Container: {0}", "ValueAudioCodec": "Audio Codec: {0}", @@ -524,17 +526,17 @@ "MessageInstallPluginFromApp": "Dieses Plugin muss von der App aus installiert werden, mit der du es benutzen willst.", "ValuePriceUSD": "Preis: {0} (USD)", "MessageFeatureIncludedWithSupporter": "Du bist f\u00fcr diese Funktion registriert und kannst diese durch eine aktive Unterst\u00fctzer Mitgliedschaft weiterhin nutzen.", - "MessageChangeRecurringPlanConfirm": "After completing this transaction you will need to cancel your previous recurring donation from within your PayPal account. Thank you for supporting Emby.", + "MessageChangeRecurringPlanConfirm": "Nach vollendeter Bezahlung m\u00fcssen Sie Ihre zuvor gemachten Dauer-Spenden in Ihrem PayPal Konto beenden. Vielen Dank das Sie Emby unterst\u00fctzen.", "MessageSupporterMembershipExpiredOn": "Deine Unterst\u00fctzer Mitgliedschaft endet am {0}.", - "MessageYouHaveALifetimeMembership": "You have a lifetime supporter membership. You can provide additional donations on a one-time or recurring basis using the options below. Thank you for supporting Emby.", + "MessageYouHaveALifetimeMembership": "Sie besitzen eine lebenslange Unterst\u00fctzer-Mitgliedschaft. Sie k\u00f6nnen zus\u00e4tzliche Spenden als Einmal-Zahlung oder als wiederkehrende Zahlungen t\u00e4tigen. Vielen Dank das Sie Emby unterst\u00fctzen.", "MessageYouHaveAnActiveRecurringMembership": "Du hast eine aktive {0} Mitgliedschaft. Du kannst deine Mitgliedschaft durch folgende Optionen aufwerten.", "ButtonDelete": "L\u00f6schen", - "HeaderEmbyAccountAdded": "Emby Account Added", - "MessageEmbyAccountAdded": "The Emby account has been added to this user.", - "MessagePendingEmbyAccountAdded": "The Emby account has been added to this user. An email will be sent to the owner of the account. The invitation will need to be confirmed by clicking a link within the email.", - "HeaderEmbyAccountRemoved": "Emby Account Removed", - "MessageEmbyAccontRemoved": "The Emby account has been removed from this user.", - "TooltipLinkedToEmbyConnect": "Linked to Emby Connect", + "HeaderEmbyAccountAdded": "Emby Konto hinzugef\u00fcgt", + "MessageEmbyAccountAdded": "Das Emby Konto wurde diesem Benutzer hinzugef\u00fcgt.", + "MessagePendingEmbyAccountAdded": "Das Emby Konto wurde diesem Benutzer hinzugef\u00fcgt. Eine Emails wird an den Besitzer dieses Kontos gesendet. Die Einladung muss mit einem Klick auf den Link in der Email best\u00e4tigt werden.", + "HeaderEmbyAccountRemoved": "Emby Konto entfernt", + "MessageEmbyAccontRemoved": "Das Emby Konto wurde von diesem Benutzer entfernt.", + "TooltipLinkedToEmbyConnect": "Verbunden mit Emby Connect", "HeaderUnrated": "Nicht bewertet", "ValueDiscNumber": "Disc {0}", "HeaderUnknownDate": "Unbekanntes Datum", @@ -664,12 +666,12 @@ "WebClientTourMetadataManager": "Klicke auf \"Bearbeiten\" um den Metadaten-Manager zu \u00f6ffnen", "WebClientTourPlaylists": "Erstelle einfach Wiedergabelisten und Schnellmixe und spiele diese auf jedem Ger\u00e4t", "WebClientTourCollections": "Erstelle Film Sammlungen um Box-Sets zusammenzuf\u00fchren", - "WebClientTourUserPreferences1": "User preferences allow you to customize the way your library is presented in all of your Emby apps", - "WebClientTourUserPreferences2": "Configure your audio and subtitle language settings once, for every Emby app", + "WebClientTourUserPreferences1": "Benutzereinstellungen erlauben die personalisierte Darstellung Ihrer Bibliothek in all unseren Emby Apps.", + "WebClientTourUserPreferences2": "Legen Sie die Sprachen f\u00fcr Audio und Untertitel einmalig f\u00fcr alle Emby Apps fest.", "WebClientTourUserPreferences3": "Designe die Web-Client-Homepage nach deinen W\u00fcnschen", "WebClientTourUserPreferences4": "Konfiguriere Hintergr\u00fcnde, Titelsongs und externe Abspieler", "WebClientTourMobile1": "Der Web-Client funktioniert hervorragend auf Smartphones und Tablets ...", - "WebClientTourMobile2": "and easily controls other devices and Emby apps", + "WebClientTourMobile2": "und steuern Sie auf einfache Weise andere Ger\u00e4te und Emby Apps", "WebClientTourMySync": "Synchronisieren Sie pers\u00f6nliche Medien mit Ihren Ger\u00e4ten um diese offline anzuschauen.", "MessageEnjoyYourStay": "Genie\u00dfe deinen Aufenthalt", "DashboardTourDashboard": "Die Server\u00fcbersicht erlaubt es dir deinen Server und dessen Benutzer im Blick zu behalten. Somit wei\u00dft du immer wer gerade was macht und wo er sich befindet.", @@ -681,7 +683,7 @@ "DashboardTourPlugins": "Installiere Plugins wie Internet Videoportale, Live-TV, Metadatenscanner und mehr.", "DashboardTourNotifications": "Sende automatisch Benachrichtigungen von Serverereignissen auf dein mobiles Endger\u00e4t, per E-Mail und mehr.", "DashboardTourScheduledTasks": "Verwalte einfach lang dauernde Aufgaben mit Hilfe von geplanten Aufgaben. Entscheide wann diese ausgef\u00fchrt werden und wie oft.", - "DashboardTourMobile": "The Emby Server dashboard works great on smartphones and tablets. Manage your server from the palm of your hand anytime, anywhere.", + "DashboardTourMobile": "Die Emby Server Startseite funktioniert super auf Smartphones oder Tabletts. Kontrollieren Sie Ihren Server zu jeder Zeit, egal wo.", "DashboardTourSync": "Synchronisieren Sie pers\u00f6nliche Medien mit Ihren Ger\u00e4ten um diese offline anzuschauen.", "MessageRefreshQueued": "Warteschlange aktualisieren", "TabDevices": "Ger\u00e4te", @@ -691,13 +693,13 @@ "DeleteDeviceConfirmation": "Bist du dir sicher dieses Ger\u00e4t l\u00f6schen zu wollen? Es wird wieder angezeigt werden, sobald sich ein Uder dar\u00fcber einloggt.", "LabelEnableCameraUploadFor": "Aktiviere den Kamera-Upload f\u00fcr:", "HeaderSelectUploadPath": "W\u00e4hle Upload Pfad", - "LabelEnableCameraUploadForHelp": "Uploads will occur automatically in the background when signed into Emby.", + "LabelEnableCameraUploadForHelp": "Uploads erscheinen automatisch im Hintergrund wenn Sie mit Emby verbunden sind.", "ErrorMessageStartHourGreaterThanEnd": "Die Endzeit muss gr\u00f6\u00dfer als die Startzeit sein.", "ButtonLibraryAccess": "Bibliothekszugang", "ButtonParentalControl": "Kindersicherung", "HeaderInvitationSent": "Einladung verschickt", "MessageInvitationSentToUser": "Eine E-Mail mit der Einladung zum Sharing ist an {0} geschickt worden.", - "MessageInvitationSentToNewUser": "An email has been sent to {0} inviting them to sign up with Emby.", + "MessageInvitationSentToNewUser": "Eine Email wurde an {0} mit einer Einladung zur Anmeldung an Emby gesendet.", "HeaderConnectionFailure": "Verbindungsfehler", "MessageUnableToConnectToServer": "Wir k\u00f6nnen gerade keine Verbindung zum gew\u00e4hlten Server herstellen. Bitte stellen Sie sicher das dieser l\u00e4uft und versuchen Sie es erneut.", "ButtonSelectServer": "W\u00e4hle Server", @@ -714,8 +716,8 @@ "MessageInvalidForgotPasswordPin": "Ein ung\u00fcltiger oder abgelaufener PIN wurde eingegeben. Bitte versuche es noch einmal.", "MessagePasswordResetForUsers": "Passw\u00f6rter der folgenden Benutzer wurden entfernt:", "HeaderInviteGuest": "Lade G\u00e4ste ein", - "ButtonLinkMyEmbyAccount": "Link my account now", - "MessageConnectAccountRequiredToInviteGuest": "In order to invite guests you need to first link your Emby account to this server.", + "ButtonLinkMyEmbyAccount": "Verbinde mein Konto jetzt", + "MessageConnectAccountRequiredToInviteGuest": "Um G\u00e4ste einladen zu k\u00f6nnen, m\u00fcssen Sie erst Ihr Emby Konto mit diesem Server verbinden.", "ButtonSync": "Synchronisieren", "SyncMedia": "Synchronisiere Medien", "HeaderCancelSyncJob": "Synchronisierung abbrechen", diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/el.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/el.json index 31efea1b78..336e311e64 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/el.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/el.json @@ -35,6 +35,8 @@ "HeaderConfirmation": "Confirmation", "MessageKeyUpdated": "Thank you. Your supporter key has been updated.", "MessageKeyRemoved": "Thank you. Your supporter key has been removed.", + "TitleLiveTV": "Live TV", + "TitleSync": "Sync", "ErrorLaunchingChromecast": "There was an error launching chromecast. Please ensure your device is connected to your wireless network.", "MessageErrorLoadingSupporterInfo": "There was an error loading supporter information. Please try again later.", "MessageLinkYourSupporterKey": "Link your supporter key with up to {0} Emby Connect members to enjoy free access to the following apps:", @@ -93,7 +95,6 @@ "HeaderWelcomeToProjectWebClient": "Welcome to the Emby Web Client", "ButtonTakeTheTour": "Take the tour", "HeaderWelcomeBack": "Welcome back!", - "TitleSync": "Sync", "TitlePlugins": "Plugins", "ButtonTakeTheTourToSeeWhatsNew": "Take the tour to see what's new", "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.", @@ -119,6 +120,7 @@ "MessagePlaybackErrorNotAllowed": "You're currently not authorized to play this content. Please contact your system administrator for details.", "MessagePlaybackErrorNoCompatibleStream": "No compatible streams are currently available. Please try again later.", "MessagePlaybackErrorRateLimitExceeded": "Your playback rate limit has been exceeded. Please contact your system administrator for details.", + "MessagePlaybackErrorPlaceHolder": "The content chosen is not playable from this device.", "HeaderSelectAudio": "Select Audio", "HeaderSelectSubtitles": "Select Subtitles", "ButtonMarkForRemoval": "Remove from device", diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/en_GB.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/en_GB.json index 937d1fa431..e6509b9daf 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/en_GB.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/en_GB.json @@ -35,6 +35,8 @@ "HeaderConfirmation": "Confirmation", "MessageKeyUpdated": "Thank you. Your supporter key has been updated.", "MessageKeyRemoved": "Thank you. Your supporter key has been removed.", + "TitleLiveTV": "Live TV", + "TitleSync": "Sync", "ErrorLaunchingChromecast": "There was an error launching chromecast. Please ensure your device is connected to your wireless network.", "MessageErrorLoadingSupporterInfo": "There was an error loading supporter information. Please try again later.", "MessageLinkYourSupporterKey": "Link your supporter key with up to {0} Emby Connect members to enjoy free access to the following apps:", @@ -93,7 +95,6 @@ "HeaderWelcomeToProjectWebClient": "Welcome to the Emby Web Client", "ButtonTakeTheTour": "Take the tour", "HeaderWelcomeBack": "Welcome back!", - "TitleSync": "Sync", "TitlePlugins": "Plugins", "ButtonTakeTheTourToSeeWhatsNew": "Take the tour to see what's new", "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.", @@ -119,6 +120,7 @@ "MessagePlaybackErrorNotAllowed": "You're currently not authorized to play this content. Please contact your system administrator for details.", "MessagePlaybackErrorNoCompatibleStream": "No compatible streams are currently available. Please try again later.", "MessagePlaybackErrorRateLimitExceeded": "Your playback rate limit has been exceeded. Please contact your system administrator for details.", + "MessagePlaybackErrorPlaceHolder": "The content chosen is not playable from this device.", "HeaderSelectAudio": "Select Audio", "HeaderSelectSubtitles": "Select Subtitles", "ButtonMarkForRemoval": "Remove from device", diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/en_US.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/en_US.json index a071cf400b..bc6c5aaf10 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/en_US.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/en_US.json @@ -35,6 +35,8 @@ "HeaderConfirmation": "Confirmation", "MessageKeyUpdated": "Thank you. Your supporter key has been updated.", "MessageKeyRemoved": "Thank you. Your supporter key has been removed.", + "TitleLiveTV": "Live TV", + "TitleSync": "Sync", "ErrorLaunchingChromecast": "There was an error launching chromecast. Please ensure your device is connected to your wireless network.", "MessageErrorLoadingSupporterInfo": "There was an error loading supporter information. Please try again later.", "MessageLinkYourSupporterKey": "Link your supporter key with up to {0} Emby Connect members to enjoy free access to the following apps:", @@ -93,7 +95,6 @@ "HeaderWelcomeToProjectWebClient": "Welcome to the Emby Web Client", "ButtonTakeTheTour": "Take the tour", "HeaderWelcomeBack": "Welcome back!", - "TitleSync": "Sync", "TitlePlugins": "Plugins", "ButtonTakeTheTourToSeeWhatsNew": "Take the tour to see what's new", "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.", @@ -119,6 +120,7 @@ "MessagePlaybackErrorNotAllowed": "You're currently not authorized to play this content. Please contact your system administrator for details.", "MessagePlaybackErrorNoCompatibleStream": "No compatible streams are currently available. Please try again later.", "MessagePlaybackErrorRateLimitExceeded": "Your playback rate limit has been exceeded. Please contact your system administrator for details.", + "MessagePlaybackErrorPlaceHolder": "The content chosen is not playable from this device.", "HeaderSelectAudio": "Select Audio", "HeaderSelectSubtitles": "Select Subtitles", "ButtonMarkForRemoval": "Remove from device", diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/es.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/es.json index ca88d8c285..981286e127 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/es.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/es.json @@ -35,6 +35,8 @@ "HeaderConfirmation": "Confirmaci\u00f3n", "MessageKeyUpdated": "Gracias. Su clave de seguidor ha sido actualizada.", "MessageKeyRemoved": "Gracias. Su clave de seguidor ha sido eliminada.", + "TitleLiveTV": "Tv en vivo", + "TitleSync": "Sync", "ErrorLaunchingChromecast": "Ha habido un error al lanzar chromecast. Asegurese que su dispositivo est\u00e1 conectado a su red inal\u00e1mbrica.", "MessageErrorLoadingSupporterInfo": "There was an error loading supporter information. Please try again later.", "MessageLinkYourSupporterKey": "Link your supporter key with up to {0} Emby Connect members to enjoy free access to the following apps:", @@ -93,7 +95,6 @@ "HeaderWelcomeToProjectWebClient": "Welcome to the Emby Web Client", "ButtonTakeTheTour": "Take the tour", "HeaderWelcomeBack": "Welcome back!", - "TitleSync": "Sync", "TitlePlugins": "Plugins", "ButtonTakeTheTourToSeeWhatsNew": "Take the tour to see what's new", "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.", @@ -119,6 +120,7 @@ "MessagePlaybackErrorNotAllowed": "You're currently not authorized to play this content. Please contact your system administrator for details.", "MessagePlaybackErrorNoCompatibleStream": "No compatible streams are currently available. Please try again later.", "MessagePlaybackErrorRateLimitExceeded": "Your playback rate limit has been exceeded. Please contact your system administrator for details.", + "MessagePlaybackErrorPlaceHolder": "The content chosen is not playable from this device.", "HeaderSelectAudio": "Seleccionar Audio", "HeaderSelectSubtitles": "Seleccionar Subt\u00edtulos", "ButtonMarkForRemoval": "Remove from device", diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/es_MX.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/es_MX.json index 86b0c66362..b0700c49c9 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/es_MX.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/es_MX.json @@ -35,6 +35,8 @@ "HeaderConfirmation": "Confirmaci\u00f3n", "MessageKeyUpdated": "Gracias. Su clave de aficionado ha sido actualizada.", "MessageKeyRemoved": "Gracias. Su clave de aficionado ha sido eliminada.", + "TitleLiveTV": "TV en Vivo", + "TitleSync": "Sinc", "ErrorLaunchingChromecast": "Hubo un error iniciando chromecast. Por favor aseg\u00farate de que tu dispositivo este conectado a tu red inalambrica", "MessageErrorLoadingSupporterInfo": "Se present\u00f3 un error al cargar la informaci\u00f3n del aficionado. Por favor int\u00e9ntelo m\u00e1s tarde.", "MessageLinkYourSupporterKey": "Enlaza tu clave de aficionado con hasta {0} miembros de Emby Connect para disfrutar de acceso gratuito a la siguientes aplicaciones:", @@ -93,7 +95,6 @@ "HeaderWelcomeToProjectWebClient": "Bienvenido al Cliente Web de Emby", "ButtonTakeTheTour": "Haga el recorrido", "HeaderWelcomeBack": "\u00a1Bienvenido nuevamente!", - "TitleSync": "Sinc", "TitlePlugins": "Complementos", "ButtonTakeTheTourToSeeWhatsNew": "Inice el tour para ver que hay de nuevo", "MessageNoSyncJobsFound": "No se han encontrado trabajos de sincronizaci\u00f3n. Cree trabajos de sincronizaci\u00f3n empleando los botones de Sinc que se encuentran en la intergface web.", @@ -119,6 +120,7 @@ "MessagePlaybackErrorNotAllowed": "Actualmente no esta autorizado para reproducir este contenido. Por favor contacte a su administrador de sistema para mas informaci\u00f3n.", "MessagePlaybackErrorNoCompatibleStream": "No hay streams compatibles en este en este momento. Por favor intente de nuevo mas tarde.", "MessagePlaybackErrorRateLimitExceeded": "Su limite de transferencia ha sido excedido. Por favor contacte a su administrador de sistema para mas informaci\u00f3n.", + "MessagePlaybackErrorPlaceHolder": "The content chosen is not playable from this device.", "HeaderSelectAudio": "Seleccionar Audio", "HeaderSelectSubtitles": "Seleccionar Subtitulos", "ButtonMarkForRemoval": "Remover de dispositivo", @@ -192,7 +194,7 @@ "MessagePleaseSelectOneItem": "Por favor selecciona al menos un elemento.", "MessagePleaseSelectTwoItems": "Por favor selecciona al menos dos elementos.", "MessageTheFollowingItemsWillBeGrouped": "Los siguientes t\u00edtulos ser\u00e1n agrupados en un solo elemento.", - "MessageConfirmItemGrouping": "La aplicaciones Emby eligira autom\u00e1ticamente la versi\u00f3n optima para reproducir basado en el dispositivo y rendimiento de red. \u00bfEsta seguro de que desea continuar?", + "MessageConfirmItemGrouping": "Las aplicaciones Emby eligiran autom\u00e1ticamente la versi\u00f3n optima para reproducir basado en el dispositivo y rendimiento de red. \u00bfEsta seguro de que desea continuar?", "HeaderResume": "Continuar", "HeaderMyViews": "Mis Vistas", "HeaderLibraryFolders": "Carpetas de Medios", @@ -210,12 +212,12 @@ "HeaderSelectTranscodingPath": "Seleccionar Ruta para Transcodificaci\u00f3n Temporal", "HeaderSelectImagesByNamePath": "Seleccionar Ruta para Im\u00e1genes por Nombre", "HeaderSelectMetadataPath": "Seleccionar Ruta para Metadatos", - "HeaderSelectServerCachePathHelp": "Explore o capture la ruta a utilizar para los archivos del cach\u00e9 del servidor. La carpeta debe tener permisos de escritura.", - "HeaderSelectTranscodingPathHelp": "Explore o capture la ruta a utilizar para los archivos temporales de transcodificaci\u00f3n. La carpeta debe tener permisos de escritura.", - "HeaderSelectImagesByNamePathHelp": "Explore o capture la ruta a utilizar para la carpeta de im\u00e1genes por nombre. La carpeta debe tener permisos de escritura.", - "HeaderSelectMetadataPathHelp": "Explore o capture la ruta donde desea almacenar los metadatos. La carpeta debe tener permisos de escritura.", + "HeaderSelectServerCachePathHelp": "Explore o introduzca la ruta a utilizar para los archivos del cach\u00e9 del servidor. La carpeta debe tener permisos de escritura.", + "HeaderSelectTranscodingPathHelp": "Explore o introduzca la ruta a utilizar para los archivos temporales de transcodificaci\u00f3n. La carpeta debe tener permisos de escritura.", + "HeaderSelectImagesByNamePathHelp": "Explore o introduzca la ruta a utilizar para la carpeta de im\u00e1genes por nombre. La carpeta debe tener permisos de escritura.", + "HeaderSelectMetadataPathHelp": "Explore o introduzca la ruta donde desea almacenar los metadatos. La carpeta debe tener permisos de escritura.", "HeaderSelectChannelDownloadPath": "Selecciona una ruta para la descarga del canal", - "HeaderSelectChannelDownloadPathHelp": "Navega o entra en la ruta usada para almacenar los archivos temporales del canal. La carpeta debe tener permisos de escritura.", + "HeaderSelectChannelDownloadPathHelp": "Explore o introduzca la ruta usada para almacenar los archivos temporales del canal. La carpeta debe tener permisos de escritura.", "OptionNewCollection": "Nuevo...", "ButtonAdd": "Agregar", "ButtonRemove": "Eliminar", @@ -236,12 +238,12 @@ "MessageFollowingFileWillBeMovedFrom": "El siguiente archivo sera movido de:", "MessageDestinationTo": "a:", "HeaderSelectWatchFolder": "Elegir Carpeta Monitoreada", - "HeaderSelectWatchFolderHelp": "Navega o introduce la ruta hacia tu carpeta monitoreada. La carpeta debe tener permisos de escritura.", + "HeaderSelectWatchFolderHelp": "Explore o introduzca la ruta para la carpeta para monitorear. La carpeta debe tener permisos de escritura.", "OrganizePatternResult": "Resultado: {0}", "HeaderRestart": "Reiniciar", "HeaderShutdown": "Apagar", "MessageConfirmRestart": "\u00bfEsta seguro de que desea reiniciar el Servidor Emby?", - "MessageConfirmShutdown": "\u00bfEsta seguro de que desea apagar el Servidor Emby?", + "MessageConfirmShutdown": "\u00bfEsta seguro de que desea detener el Servidor Emby?", "ButtonUpdateNow": "Actualizar Ahora", "ValueItemCount": "{0} \u00edtem", "ValueItemCountPlural": "{0} \u00edtems", @@ -271,7 +273,7 @@ "HeaderSelectMediaPath": "Seleccionar ruta a medios", "HeaderSelectPath": "Seleccionar Trayectoria", "ButtonNetwork": "Red", - "MessageDirectoryPickerInstruction": "Las rutas de red pueden ser ingresadas manualmente en caso de que el bot\u00f3n de Red no pueda localizar sus dispositivos. Por ejemplo, {0} or {1}.", + "MessageDirectoryPickerInstruction": "Las rutas de red pueden ser introducidas manualmente en caso de que el bot\u00f3n de Red no pueda localizar sus dispositivos. Por ejemplo, {0} or {1}.", "HeaderMenu": "Men\u00fa", "ButtonOpen": "Abrir", "ButtonOpenInNewTab": "Abrir en una pesta\u00f1a nueva", @@ -310,7 +312,7 @@ "OptionBlockLiveTvChannels": "Canales de TV en Vivo", "OptionBlockChannelContent": "Contenido de Canales de Internet", "ButtonRevoke": "Revocar", - "MessageConfirmRevokeApiKey": "\u00bfEsta seguro de que desea revocar esta clave api? La conexi\u00f3n de la aplicaci\u00f3n a Emby sera terminada abruptamente.", + "MessageConfirmRevokeApiKey": "\u00bfEsta seguro de que desea revocar esta clave api? La conexi\u00f3n de la aplicaci\u00f3n con el Servidor Emby sera terminada abruptamente.", "HeaderConfirmRevokeApiKey": "Revocar llave de API", "ValueContainer": "Contenedor: {0}", "ValueAudioCodec": "C\u00f3dec de Audio: {0}", @@ -328,8 +330,8 @@ "ButtonBrowseOnlineImages": "Buscar im\u00e1genes en l\u00ednea", "HeaderDeleteItem": "Eliminar \u00cdtem", "ConfirmDeleteItem": "Al eliminar este \u00edtem se eliminar\u00e1 tanto del sistema de archivos como de su biblioteca de medios. \u00bfEsta seguro de querer continuar?", - "MessagePleaseEnterNameOrId": "Por favor ingrese un nombre o id externo.", - "MessageValueNotCorrect": "El valor ingresado no es correcto. Intente nuevamente por favor.", + "MessagePleaseEnterNameOrId": "Por favor introduzca un nombre o id externo.", + "MessageValueNotCorrect": "El valor introducido no es correcto. Intente nuevamente por favor.", "MessageItemSaved": "\u00cdtem guardado.", "MessagePleaseAcceptTermsOfServiceBeforeContinuing": "Por favor acepte los t\u00e9rminos del servicio antes de continuar.", "OptionEnded": "Finalizado", @@ -442,7 +444,7 @@ "OptionGames": "Juegos", "OptionGameSystems": "Sistemas de juegos", "OptionMusicArtists": "Int\u00e9rpretes", - "OptionMusicAlbums": "\u00c1lbums musicales", + "OptionMusicAlbums": "\u00c1lbumes musicales", "OptionMusicVideos": "Videos musicales", "OptionSongs": "Canciones", "OptionHomeVideos": "Videos caseros", @@ -508,7 +510,7 @@ "TabEpisodes": "Episodios", "TabTrailers": "Avances", "TabGames": "Juegos", - "TabAlbums": "\u00c1lbums", + "TabAlbums": "\u00c1lbumes", "TabSongs": "Canciones", "TabMusicVideos": "Videos Musicales", "BirthPlaceValue": "Lugar de nacimiento: {0}", @@ -524,7 +526,7 @@ "MessageInstallPluginFromApp": "El complemento debe estar instalado desde la aplicaci\u00f3n en la que va a utilizarlo.", "ValuePriceUSD": "Precio: {0} (USD)", "MessageFeatureIncludedWithSupporter": "Se encuentra registrado para esta caracter\u00edstica, y podr\u00e1 continuar us\u00e1ndola con una membres\u00eda de aficionado activa.", - "MessageChangeRecurringPlanConfirm": "Despu\u00e9s de completar esta transacci\u00f3n necesitara cancelar su donaci\u00f3n recurrente previa desde su cuenta PayPal. Gracias por apoyar Emby", + "MessageChangeRecurringPlanConfirm": "Despu\u00e9s de completar esta transacci\u00f3n necesitar\u00e1 cancelar su donaci\u00f3n recurrente previa desde su cuenta PayPal. Gracias por apoyar Emby.", "MessageSupporterMembershipExpiredOn": "Su membres\u00eda de aficionado expir\u00f3 en {0}.", "MessageYouHaveALifetimeMembership": "Usted cuenta con una membres\u00eda de aficionado vitalicia. Puede realizar donaciones adicionales individuales o recurrentes usando las opciones siguientes. Gracias por apoyar a Emby.", "MessageYouHaveAnActiveRecurringMembership": "Usted cuenta con membres\u00eda {0} activa. Puede mejorarla usando las opciones siguientes.", @@ -561,7 +563,7 @@ "ValueOneGame": "1 juego", "ValueGameCount": "{0} juegos", "ValueOneAlbum": "1 \u00e1lbum", - "ValueAlbumCount": "{0} \u00e1lbums", + "ValueAlbumCount": "{0} \u00e1lbumes", "ValueOneSong": "1 canci\u00f3n", "ValueSongCount": "{0} canciones", "ValueOneMusicVideo": "1 video musical", @@ -606,7 +608,7 @@ "HeaderIfYouLikeCheckTheseOut": "Si te gust\u00f3 {0}, prueba con...", "HeaderPlotKeywords": "Palabras clave de la Trama", "HeaderMovies": "Pel\u00edculas", - "HeaderAlbums": "\u00c1lbums", + "HeaderAlbums": "\u00c1lbumes", "HeaderGames": "Juegos", "HeaderBooks": "Libros", "HeaderEpisodes": "Episodios", @@ -664,7 +666,7 @@ "WebClientTourMetadataManager": "Haga clic en editar para abrir el administrador de metadatos", "WebClientTourPlaylists": "Cree f\u00e1cilmente listas de reproducci\u00f3n y mezclas instant\u00e1neas, y reprod\u00fazcalas en cualquier dispositivo", "WebClientTourCollections": "Cree colecciones de pel\u00edculas para agruparlas en sets de pel\u00edculas", - "WebClientTourUserPreferences1": "Las preferencias de usuario permiten personalizar la manera que que su biblioteca es mostrada en todas tus aplicaciones Emby.", + "WebClientTourUserPreferences1": "Las preferencias de usuario le permiten personalizar la manera que que su biblioteca es mostrada en todas tus aplicaciones Emby.", "WebClientTourUserPreferences2": "Configure sus preferencias de audio y subtitulos una vez, por cada aplicaci\u00f3n Emby.", "WebClientTourUserPreferences3": "Dise\u00f1e a su gusto la p\u00e1gina principal del cliente web", "WebClientTourUserPreferences4": "Configure im\u00e1genes de fondo, canciones de tema y reproductores externos", @@ -681,7 +683,7 @@ "DashboardTourPlugins": "Instale complementos como canales de video de Internet, TV en vivo, buscadores de metadatos y m\u00e1s.", "DashboardTourNotifications": "Env\u00ede notificaciones automatizadas de eventos del servidor a sus dispositivos m\u00f3viles, correo electr\u00f3nico y m\u00e1s.", "DashboardTourScheduledTasks": "Administre f\u00e1cilmente operaciones de larga duraci\u00f3n con tareas programadas. Decida cuando se ejecutar\u00e1n y con que periodicidad.", - "DashboardTourMobile": "El panel de control del Servidor Emby funciona genial en smartphones y tablets. Administra tu servidor desde la palma de tu mano en cualquier momento y en cualquier lugar.", + "DashboardTourMobile": "El panel de control del Servidor Emby funciona genial en smartphones y tablets. Administre su servidor desde la palma de su mano en cualquier momento y en cualquier lugar.", "DashboardTourSync": "Sincronice sus medios personales a sus dispositivos para reproducirlos fuera de l\u00ednea.", "MessageRefreshQueued": "Actualizaci\u00f3n programada", "TabDevices": "Dispositivos", @@ -691,13 +693,13 @@ "DeleteDeviceConfirmation": "\u00bfEst\u00e1 seguro de querer eliminar este dispositivo? Volver\u00e1 a aparecer la siguiente vez que un usuario inicie sesi\u00f3n en \u00e9l.", "LabelEnableCameraUploadFor": "Habilitar subir desde la c\u00e1mara para:", "HeaderSelectUploadPath": "Seleccionar ruta de subida", - "LabelEnableCameraUploadForHelp": "La transferencias ocurrir\u00e1n autom\u00e1ticamente en el fondo cuando acceda a Emby", + "LabelEnableCameraUploadForHelp": "La transferencias ocurrir\u00e1n autom\u00e1ticamente en segundo plano cuando haya iniciado sesi\u00f3n en Emby.", "ErrorMessageStartHourGreaterThanEnd": "El horario de fin debe ser mayor al de comienzo.", "ButtonLibraryAccess": "Acceso a biblioteca", "ButtonParentalControl": "Control parental", "HeaderInvitationSent": "Invitaci\u00f3n Enviada", "MessageInvitationSentToUser": "Se ha enviado un correo electr\u00f3nico a {0}, invit\u00e1ndolo a aceptar tu invitaci\u00f3n para compartir.", - "MessageInvitationSentToNewUser": "Un correo electr\u00f3nico se ha enviado a {0} invit\u00e1ndolo a ingresar con Emby.", + "MessageInvitationSentToNewUser": "Un correo electr\u00f3nico se ha enviado a {0} invit\u00e1ndolos a registrarse en Emby.", "HeaderConnectionFailure": "Falla de Conexi\u00f3n", "MessageUnableToConnectToServer": "No es posible conectarse al servidor seleccionado en este momento. Por favor aseg\u00farese de que se encuentra en ejecuci\u00f3n e int\u00e9ntelo nuevamente.", "ButtonSelectServer": "Seleccionar servidor", @@ -715,7 +717,7 @@ "MessagePasswordResetForUsers": "Las contrase\u00f1as han sido eliminadas de los siguientes usuarios:", "HeaderInviteGuest": "Agregar un Invitado", "ButtonLinkMyEmbyAccount": "Enlazar mi cuenta ahora", - "MessageConnectAccountRequiredToInviteGuest": "Para poder enviar invitaciones necesita primero enlazar su cuenta Emby en este servidor.", + "MessageConnectAccountRequiredToInviteGuest": "Para poder enviar invitaciones necesita primero enlazar su cuenta Emby con este servidor.", "ButtonSync": "SInc", "SyncMedia": "Sincronizar Medios", "HeaderCancelSyncJob": "Cancelar Sinc.", diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/fi.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/fi.json index 4963cffca6..7fcd26b324 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/fi.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/fi.json @@ -35,6 +35,8 @@ "HeaderConfirmation": "Confirmation", "MessageKeyUpdated": "Thank you. Your supporter key has been updated.", "MessageKeyRemoved": "Thank you. Your supporter key has been removed.", + "TitleLiveTV": "Live TV", + "TitleSync": "Sync", "ErrorLaunchingChromecast": "There was an error launching chromecast. Please ensure your device is connected to your wireless network.", "MessageErrorLoadingSupporterInfo": "There was an error loading supporter information. Please try again later.", "MessageLinkYourSupporterKey": "Link your supporter key with up to {0} Emby Connect members to enjoy free access to the following apps:", @@ -93,7 +95,6 @@ "HeaderWelcomeToProjectWebClient": "Welcome to the Emby Web Client", "ButtonTakeTheTour": "Take the tour", "HeaderWelcomeBack": "Welcome back!", - "TitleSync": "Sync", "TitlePlugins": "Plugins", "ButtonTakeTheTourToSeeWhatsNew": "Take the tour to see what's new", "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.", @@ -119,6 +120,7 @@ "MessagePlaybackErrorNotAllowed": "You're currently not authorized to play this content. Please contact your system administrator for details.", "MessagePlaybackErrorNoCompatibleStream": "No compatible streams are currently available. Please try again later.", "MessagePlaybackErrorRateLimitExceeded": "Your playback rate limit has been exceeded. Please contact your system administrator for details.", + "MessagePlaybackErrorPlaceHolder": "The content chosen is not playable from this device.", "HeaderSelectAudio": "Select Audio", "HeaderSelectSubtitles": "Select Subtitles", "ButtonMarkForRemoval": "Remove from device", diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/fr.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/fr.json index d4b993af32..48b51da036 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/fr.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/fr.json @@ -35,6 +35,8 @@ "HeaderConfirmation": "Confirmation", "MessageKeyUpdated": "Merci. Votre cl\u00e9 de supporteur a \u00e9t\u00e9 mise \u00e0 jour.", "MessageKeyRemoved": "Merci. Votre cl\u00e9 de supporteur a \u00e9t\u00e9 supprim\u00e9e.", + "TitleLiveTV": "TV en direct", + "TitleSync": "Sync.", "ErrorLaunchingChromecast": "Une erreur a \u00e9t\u00e9 rencontr\u00e9e lors du lancement de Chromecast. Veuillez vous assurer que votre appareil est bien connect\u00e9 \u00e0 votre r\u00e9seau sans-fil.", "MessageErrorLoadingSupporterInfo": "Il y a eu une erreur lors du chargement des informations de supporter. Veuillez r\u00e9essayer plus tard.", "MessageLinkYourSupporterKey": "Liez votre cl\u00e9 de supporteur avec un maximum de {0} membres Emby Connect pour b\u00e9n\u00e9ficier de l'acc\u00e8s gratuit aux applications suivantes :", @@ -63,7 +65,7 @@ "LabelSeries": "S\u00e9ries", "LabelStopping": "En cours d'arr\u00eat", "LabelCancelled": "(annul\u00e9)", - "LabelFailed": "(\u00e9chou\u00e9)", + "LabelFailed": "(Echec)", "ButtonHelp": "Aide", "ButtonSave": "Sauvegarder", "ButtonDownload": "T\u00e9l\u00e9chargement", @@ -93,7 +95,6 @@ "HeaderWelcomeToProjectWebClient": "Bienvenue dans client Web Emby", "ButtonTakeTheTour": "Visite guid\u00e9e", "HeaderWelcomeBack": "Bienvenue !", - "TitleSync": "Sync.", "TitlePlugins": "Plugins", "ButtonTakeTheTourToSeeWhatsNew": "Suivez le guide pour d\u00e9couvrir les nouveaut\u00e9s", "MessageNoSyncJobsFound": "Aucune t\u00e2che de synchronisation trouv\u00e9e. Vous pouvez cr\u00e9er des t\u00e2ches de synchronisation gr\u00e2ce aux boutons 'Synchroniser' partout dans l'interface web.", @@ -119,6 +120,7 @@ "MessagePlaybackErrorNotAllowed": "Vous n'\u00eates pas autoris\u00e9 \u00e0 lire ce contenu. Veuillez contacter votre administrateur syst\u00e8me pour plus de d\u00e9tails.", "MessagePlaybackErrorNoCompatibleStream": "Aucun flux compatible n'est actuellement disponible. Veuillez r\u00e9essayer plus tard.", "MessagePlaybackErrorRateLimitExceeded": "Vous avez d\u00e9pass\u00e9 votre limite de lecture. Veuillez contacter votre administrateur syst\u00e8me pour plus de d\u00e9tails.", + "MessagePlaybackErrorPlaceHolder": "The content chosen is not playable from this device.", "HeaderSelectAudio": "S\u00e9lectionner audio", "HeaderSelectSubtitles": "S\u00e9lectionner sous-titres", "ButtonMarkForRemoval": "Supprimer de l'appareil", diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/he.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/he.json index d1d241e068..a92346cb2e 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/he.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/he.json @@ -35,6 +35,8 @@ "HeaderConfirmation": "Confirmation", "MessageKeyUpdated": "Thank you. Your supporter key has been updated.", "MessageKeyRemoved": "Thank you. Your supporter key has been removed.", + "TitleLiveTV": "\u05d8\u05dc\u05d5\u05d5\u05d9\u05d6\u05d9\u05d4 \u05d7\u05d9\u05d9\u05d4", + "TitleSync": "Sync", "ErrorLaunchingChromecast": "There was an error launching chromecast. Please ensure your device is connected to your wireless network.", "MessageErrorLoadingSupporterInfo": "There was an error loading supporter information. Please try again later.", "MessageLinkYourSupporterKey": "Link your supporter key with up to {0} Emby Connect members to enjoy free access to the following apps:", @@ -93,7 +95,6 @@ "HeaderWelcomeToProjectWebClient": "Welcome to the Emby Web Client", "ButtonTakeTheTour": "Take the tour", "HeaderWelcomeBack": "Welcome back!", - "TitleSync": "Sync", "TitlePlugins": "Plugins", "ButtonTakeTheTourToSeeWhatsNew": "Take the tour to see what's new", "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.", @@ -119,6 +120,7 @@ "MessagePlaybackErrorNotAllowed": "You're currently not authorized to play this content. Please contact your system administrator for details.", "MessagePlaybackErrorNoCompatibleStream": "No compatible streams are currently available. Please try again later.", "MessagePlaybackErrorRateLimitExceeded": "Your playback rate limit has been exceeded. Please contact your system administrator for details.", + "MessagePlaybackErrorPlaceHolder": "The content chosen is not playable from this device.", "HeaderSelectAudio": "Select Audio", "HeaderSelectSubtitles": "Select Subtitles", "ButtonMarkForRemoval": "Remove from device", diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/hr.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/hr.json index 3e080ed6a5..3d2255b83f 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/hr.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/hr.json @@ -35,6 +35,8 @@ "HeaderConfirmation": "Confirmation", "MessageKeyUpdated": "Thank you. Your supporter key has been updated.", "MessageKeyRemoved": "Thank you. Your supporter key has been removed.", + "TitleLiveTV": "TV", + "TitleSync": "Sync", "ErrorLaunchingChromecast": "There was an error launching chromecast. Please ensure your device is connected to your wireless network.", "MessageErrorLoadingSupporterInfo": "There was an error loading supporter information. Please try again later.", "MessageLinkYourSupporterKey": "Link your supporter key with up to {0} Emby Connect members to enjoy free access to the following apps:", @@ -93,7 +95,6 @@ "HeaderWelcomeToProjectWebClient": "Welcome to the Emby Web Client", "ButtonTakeTheTour": "Take the tour", "HeaderWelcomeBack": "Welcome back!", - "TitleSync": "Sync", "TitlePlugins": "Plugins", "ButtonTakeTheTourToSeeWhatsNew": "Take the tour to see what's new", "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.", @@ -119,6 +120,7 @@ "MessagePlaybackErrorNotAllowed": "You're currently not authorized to play this content. Please contact your system administrator for details.", "MessagePlaybackErrorNoCompatibleStream": "No compatible streams are currently available. Please try again later.", "MessagePlaybackErrorRateLimitExceeded": "Your playback rate limit has been exceeded. Please contact your system administrator for details.", + "MessagePlaybackErrorPlaceHolder": "The content chosen is not playable from this device.", "HeaderSelectAudio": "Select Audio", "HeaderSelectSubtitles": "Select Subtitles", "ButtonMarkForRemoval": "Remove from device", diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/hu.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/hu.json index f514428738..f9ddac88f5 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/hu.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/hu.json @@ -35,6 +35,8 @@ "HeaderConfirmation": "Confirmation", "MessageKeyUpdated": "Thank you. Your supporter key has been updated.", "MessageKeyRemoved": "Thank you. Your supporter key has been removed.", + "TitleLiveTV": "Live TV", + "TitleSync": "Sync", "ErrorLaunchingChromecast": "There was an error launching chromecast. Please ensure your device is connected to your wireless network.", "MessageErrorLoadingSupporterInfo": "There was an error loading supporter information. Please try again later.", "MessageLinkYourSupporterKey": "Link your supporter key with up to {0} Emby Connect members to enjoy free access to the following apps:", @@ -93,7 +95,6 @@ "HeaderWelcomeToProjectWebClient": "Welcome to the Emby Web Client", "ButtonTakeTheTour": "Take the tour", "HeaderWelcomeBack": "Welcome back!", - "TitleSync": "Sync", "TitlePlugins": "Plugins", "ButtonTakeTheTourToSeeWhatsNew": "Take the tour to see what's new", "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.", @@ -119,6 +120,7 @@ "MessagePlaybackErrorNotAllowed": "You're currently not authorized to play this content. Please contact your system administrator for details.", "MessagePlaybackErrorNoCompatibleStream": "No compatible streams are currently available. Please try again later.", "MessagePlaybackErrorRateLimitExceeded": "Your playback rate limit has been exceeded. Please contact your system administrator for details.", + "MessagePlaybackErrorPlaceHolder": "The content chosen is not playable from this device.", "HeaderSelectAudio": "Select Audio", "HeaderSelectSubtitles": "Select Subtitles", "ButtonMarkForRemoval": "Remove from device", diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/it.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/it.json index 9c816d683e..76d29661bc 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/it.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/it.json @@ -35,6 +35,8 @@ "HeaderConfirmation": "Conferma", "MessageKeyUpdated": "Grazie. La vostra chiave supporter \u00e8 stato aggiornato.", "MessageKeyRemoved": "Grazie. La vostra chiave supporter \u00e8 stata rimossa.", + "TitleLiveTV": "Tv in diretta", + "TitleSync": "Sincronizza", "ErrorLaunchingChromecast": "Si \u00e8 verificato un errore all'avvio di chromecast. Assicurati che il tuo dispositivo sia connesso alla rete wireless.", "MessageErrorLoadingSupporterInfo": "There was an error loading supporter information. Please try again later.", "MessageLinkYourSupporterKey": "Link your supporter key with up to {0} Emby Connect members to enjoy free access to the following apps:", @@ -93,7 +95,6 @@ "HeaderWelcomeToProjectWebClient": "Welcome to the Emby Web Client", "ButtonTakeTheTour": "Fai il tour", "HeaderWelcomeBack": "Ben tornato!", - "TitleSync": "Sincronizza", "TitlePlugins": "Plugins", "ButtonTakeTheTourToSeeWhatsNew": "Fai un tour per vedere cosa \u00e8 cambiato", "MessageNoSyncJobsFound": "Nessuna sincronizzazione pianificata. Creane una utilizzando i pulsanti sull'interfaccia web", @@ -119,6 +120,7 @@ "MessagePlaybackErrorNotAllowed": "Al momento non sei autorizzato a riprodurre questo contenuto. Per favore contatta l'amministratore del sistema per ulteriori dettagli", "MessagePlaybackErrorNoCompatibleStream": "Nessuna trasmissione compatibile \u00e8 al momento disponibile. Per favore riprova in seguito", "MessagePlaybackErrorRateLimitExceeded": "La tua quota di riproduzione \u00e8 stata raggiunta. Per favore contatta l'amministratore del sistema per ulteriori dettagli", + "MessagePlaybackErrorPlaceHolder": "The content chosen is not playable from this device.", "HeaderSelectAudio": "Seleziona audio", "HeaderSelectSubtitles": "Seleziona sottotitoli", "ButtonMarkForRemoval": "Rimuovi dal dispositivo", diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/kk.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/kk.json index 432bdc2a3d..596a9bdd73 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/kk.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/kk.json @@ -35,6 +35,8 @@ "HeaderConfirmation": "\u0420\u0430\u0441\u0442\u0430\u0443", "MessageKeyUpdated": "\u0416\u0430\u049b\u0442\u0430\u0443\u0448\u044b \u043a\u0456\u043b\u0442\u0456\u04a3\u0456\u0437 \u0436\u0430\u04a3\u0430\u0440\u0442\u044b\u043b\u0434\u044b.", "MessageKeyRemoved": "\u0416\u0430\u049b\u0442\u0430\u0443\u0448\u044b \u043a\u0456\u043b\u0442\u0456\u04a3\u0456\u0437 \u0430\u043b\u0430\u0441\u0442\u0430\u043b\u0434\u044b.", + "TitleLiveTV": "\u042d\u0444\u0438\u0440\u043b\u0456\u043a \u0422\u0414", + "TitleSync": "\u04ae\u043d\u0434\u0435\u0441\u0442\u0456\u0440\u0443", "ErrorLaunchingChromecast": "Chromecast \u0456\u0441\u043a\u0435 \u049b\u043e\u0441\u044b\u043b\u0443 \u043a\u0435\u0437\u0456\u043d\u0434\u0435 \u049b\u0430\u0442\u0435 \u043e\u0440\u044b\u043d \u0430\u043b\u0434\u044b. \u049a\u04b1\u0440\u044b\u043b\u0493\u044b\u04a3\u044b\u0437 \u0441\u044b\u043c\u0441\u044b\u0437 \u0436\u0435\u043b\u0456\u0433\u0435 \u049b\u043e\u0441\u044b\u043b\u0493\u0430\u043d\u044b\u043d\u0430 \u043a\u04e9\u0437 \u0436\u0435\u0442\u043a\u0456\u0437\u0456\u04a3\u0456\u0437.", "MessageErrorLoadingSupporterInfo": "\u0416\u0430\u049b\u0442\u0430\u0443\u0448\u044b \u0430\u049b\u043f\u0430\u0440\u0430\u0442\u044b\u043d \u0436\u04af\u043a\u0442\u0435\u0443 \u043a\u0435\u0437\u0456\u043d\u0434\u0435 \u049b\u0430\u0442\u0435 \u043e\u0440\u044b\u043d \u0430\u043b\u0434\u044b. \u04d8\u0440\u0435\u043a\u0435\u0442\u0442\u0456 \u043a\u0435\u0439\u0456\u043d \u049b\u0430\u0439\u0442\u0430\u043b\u0430\u04a3\u044b\u0437.", "MessageLinkYourSupporterKey": "\u041a\u0435\u043b\u0435\u0441\u0456 \u0431\u0430\u0493\u0434\u0430\u0440\u043b\u0430\u043c\u0430\u043b\u0430\u0440\u0493\u0430 \u0442\u0435\u0433\u0456\u043d \u049b\u0430\u0442\u044b\u043d\u0430\u0443\u0434\u044b \u0442\u0430\u043c\u0430\u0448\u0430\u043b\u0430\u0443 \u04af\u0448\u0456\u043d {0} Emby Connect \u043c\u04af\u0448\u0435\u0441\u0456\u043d\u0435 \u0434\u0435\u0439\u0456\u043d \u0431\u0430\u0439\u043b\u0430\u043d\u044b\u0441\u0442\u044b\u0440\u044b\u04a3\u044b\u0437.", @@ -93,7 +95,6 @@ "HeaderWelcomeToProjectWebClient": "Emby \u0493\u0430\u043b\u0430\u043c\u0442\u043e\u0440 \u043a\u043b\u0438\u0435\u043d\u0442\u0456\u043d\u0435 \u049b\u043e\u0448 \u043a\u0435\u043b\u0434\u0456\u04a3\u0456\u0437!", "ButtonTakeTheTour": "\u0410\u0440\u0430\u043b\u0430\u043f \u0448\u044b\u0493\u044b\u04a3\u044b\u0437", "HeaderWelcomeBack": "\u049a\u0430\u0439\u0442\u0430 \u043a\u0435\u043b\u0443\u0456\u04a3\u0456\u0437\u0431\u0435\u043d!", - "TitleSync": "\u04ae\u043d\u0434\u0435\u0441\u0442\u0456\u0440\u0443", "TitlePlugins": "\u041f\u043b\u0430\u0433\u0438\u043d\u0434\u0435\u0440", "ButtonTakeTheTourToSeeWhatsNew": "\u0411\u043e\u043b\u0493\u0430\u043d \u0436\u0430\u04a3\u0430\u043b\u044b\u049b\u0442\u0430\u0440\u043c\u0435\u043d \u0442\u0430\u043d\u044b\u0441\u0443", "MessageNoSyncJobsFound": "\u04ae\u043d\u0434\u0435\u0441\u0442\u0456\u0440\u0443 \u0436\u04b1\u043c\u044b\u0441\u044b \u0442\u0430\u0431\u044b\u043b\u043c\u0430\u0434\u044b. \u0412\u0435\u0431-\u0442\u0456\u043b\u0434\u0435\u0441\u0443\u0434\u0435 \u0442\u0430\u0431\u044b\u043b\u0430\u0442\u044b\u043d \u04ae\u043d\u0434\u0435\u0441\u0442\u0456\u0440\u0443 \u0442\u04af\u0439\u043c\u0435\u0448\u0456\u043a\u0442\u0435\u0440\u0456\u043d \u043f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u044b\u043f \u04af\u043d\u0434\u0435\u0441\u0442\u0456\u0440\u0443 \u0436\u04b1\u043c\u044b\u0441\u0442\u0430\u0440\u044b\u043d \u0436\u0430\u0441\u0430\u04a3\u044b\u0437.", @@ -119,6 +120,7 @@ "MessagePlaybackErrorNotAllowed": "\u041e\u0441\u044b \u043c\u0430\u0437\u043c\u04b1\u043d\u0434\u044b \u043e\u0439\u043d\u0430\u0442\u0443 \u04af\u0448\u0456\u043d \u0430\u0493\u044b\u043c\u0434\u0430 \u0441\u0456\u0437\u0433\u0435 \u0440\u04b1\u049b\u0441\u0430\u0442 \u0435\u0442\u0456\u043b\u043c\u0435\u0433\u0435\u043d. \u041c\u04d9\u043b\u0456\u043c\u0435\u0442\u0442\u0435\u0440 \u04af\u0448\u0456\u043d \u0436\u04af\u0439\u0435\u043b\u0456\u043a \u04d9\u043a\u0456\u043c\u0448\u0456\u04a3\u0456\u0437\u0433\u0435 \u0431\u0430\u0439\u043b\u0430\u043d\u044b\u0441\u044b\u04a3\u044b\u0437.", "MessagePlaybackErrorNoCompatibleStream": "\u0410\u0493\u044b\u043c\u0434\u0430 \u0435\u0448\u049b\u0430\u043d\u0434\u0430\u0439 \u0441\u044b\u0439\u044b\u0441\u044b\u043c\u0434\u044b \u0430\u0493\u044b\u043d\u0434\u0430\u0440 \u049b\u0430\u0442\u044b\u043d\u0430\u0443\u043b\u044b \u0435\u043c\u0435\u0441. \u04d8\u0440\u0435\u043a\u0435\u0442\u0442\u0456 \u043a\u0435\u0439\u0456\u043d \u049b\u0430\u0439\u0442\u0430\u043b\u0430\u04a3\u044b\u0437.", "MessagePlaybackErrorRateLimitExceeded": "\u041e\u0439\u043d\u0430\u0442\u0443 \u049b\u0430\u0440\u049b\u044b\u043d\u044b\u04a3\u044b\u0437 \u0448\u0435\u043a\u0442\u0435\u043d \u0430\u0441\u044b\u043f \u043a\u0435\u0442\u043a\u0435\u043d. \u041c\u04d9\u043b\u0456\u043c\u0435\u0442\u0442\u0435\u0440 \u04af\u0448\u0456\u043d \u0436\u04af\u0439\u0435\u043b\u0456\u043a \u04d9\u043a\u0456\u043c\u0448\u0456\u04a3\u0456\u0437\u0433\u0435 \u0431\u0430\u0439\u043b\u0430\u043d\u044b\u0441\u044b\u04a3\u044b\u0437.", + "MessagePlaybackErrorPlaceHolder": "\u0422\u0430\u04a3\u0434\u0430\u043b\u0493\u0430\u043d \u043c\u0430\u0437\u043c\u04b1\u043d \u0431\u04b1\u043b \u049b\u04b1\u0440\u044b\u043b\u0493\u044b\u0434\u0430\u043d \u043e\u0439\u043d\u0430\u0442\u044b\u043b\u0430\u0442\u044b\u043d \u0435\u043c\u0435\u0441.", "HeaderSelectAudio": "\u0414\u044b\u0431\u044b\u0441 \u0442\u0430\u04a3\u0434\u0430\u0443", "HeaderSelectSubtitles": "\u0421\u0443\u0431\u0442\u0438\u0442\u0440\u043b\u0435\u0440 \u0442\u0430\u04a3\u0434\u0430\u0443", "ButtonMarkForRemoval": "\u049a\u04b1\u0440\u044b\u043b\u0493\u044b\u0434\u0430\u043d \u0430\u043b\u0430\u0441\u0442\u0430\u0443", diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/ms.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/ms.json index db93e49a09..4da140d1d9 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/ms.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/ms.json @@ -35,6 +35,8 @@ "HeaderConfirmation": "Confirmation", "MessageKeyUpdated": "Thank you. Your supporter key has been updated.", "MessageKeyRemoved": "Thank you. Your supporter key has been removed.", + "TitleLiveTV": "Live TV", + "TitleSync": "Sync", "ErrorLaunchingChromecast": "There was an error launching chromecast. Please ensure your device is connected to your wireless network.", "MessageErrorLoadingSupporterInfo": "There was an error loading supporter information. Please try again later.", "MessageLinkYourSupporterKey": "Link your supporter key with up to {0} Emby Connect members to enjoy free access to the following apps:", @@ -93,7 +95,6 @@ "HeaderWelcomeToProjectWebClient": "Welcome to the Emby Web Client", "ButtonTakeTheTour": "Take the tour", "HeaderWelcomeBack": "Welcome back!", - "TitleSync": "Sync", "TitlePlugins": "Plugins", "ButtonTakeTheTourToSeeWhatsNew": "Take the tour to see what's new", "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.", @@ -119,6 +120,7 @@ "MessagePlaybackErrorNotAllowed": "You're currently not authorized to play this content. Please contact your system administrator for details.", "MessagePlaybackErrorNoCompatibleStream": "No compatible streams are currently available. Please try again later.", "MessagePlaybackErrorRateLimitExceeded": "Your playback rate limit has been exceeded. Please contact your system administrator for details.", + "MessagePlaybackErrorPlaceHolder": "The content chosen is not playable from this device.", "HeaderSelectAudio": "Select Audio", "HeaderSelectSubtitles": "Select Subtitles", "ButtonMarkForRemoval": "Remove from device", diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/nb.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/nb.json index ad6a9b2f89..b2c86d218b 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/nb.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/nb.json @@ -35,6 +35,8 @@ "HeaderConfirmation": "Bekreftelse", "MessageKeyUpdated": "Takk. Din supportern\u00f8kkel har blitt oppdatert.", "MessageKeyRemoved": "Takk. Din supportern\u00f8kkel har blitt fjernet.", + "TitleLiveTV": "Live TV", + "TitleSync": "Synk", "ErrorLaunchingChromecast": "Det var en feil ved start av Chromecast. Vennligst forsikre deg om at enheten har korrekt forbindelse til ditt tr\u00e5dl\u00f8se nettverk.", "MessageErrorLoadingSupporterInfo": "There was an error loading supporter information. Please try again later.", "MessageLinkYourSupporterKey": "Link your supporter key with up to {0} Emby Connect members to enjoy free access to the following apps:", @@ -93,7 +95,6 @@ "HeaderWelcomeToProjectWebClient": "Welcome to the Emby Web Client", "ButtonTakeTheTour": "Bli med p\u00e5 omvisning", "HeaderWelcomeBack": "Velkommen tilbake!", - "TitleSync": "Synk", "TitlePlugins": "Plugins", "ButtonTakeTheTourToSeeWhatsNew": "Ta en titt p\u00e5 hva som er nytt", "MessageNoSyncJobsFound": "Ingen synkroniseringsjobber funnet. Opprett en synkroniseringsjobb ved hjelp av Synkroniseringsknappene i biblioteket", @@ -119,6 +120,7 @@ "MessagePlaybackErrorNotAllowed": "You're currently not authorized to play this content. Please contact your system administrator for details.", "MessagePlaybackErrorNoCompatibleStream": "No compatible streams are currently available. Please try again later.", "MessagePlaybackErrorRateLimitExceeded": "Your playback rate limit has been exceeded. Please contact your system administrator for details.", + "MessagePlaybackErrorPlaceHolder": "The content chosen is not playable from this device.", "HeaderSelectAudio": "Velg Lyd", "HeaderSelectSubtitles": "Velg Undertekst", "ButtonMarkForRemoval": "Fjern fra enheten.", diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/nl.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/nl.json index 8bc0d90715..3709b6bc26 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/nl.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/nl.json @@ -6,8 +6,8 @@ "Administrator": "Beheerder", "Password": "Wachtwoord", "DeleteImage": "Verwijder afbeelding", - "MessageThankYouForSupporting": "Thank you for supporting Emby.", - "MessagePleaseSupportProject": "Please support Emby.", + "MessageThankYouForSupporting": "Dank u voor uw steun aan Emby", + "MessagePleaseSupportProject": "Steun Emby a.u.b.", "DeleteImageConfirmation": "Weet u zeker dat u deze afbeelding wilt verwijderen?", "FileReadCancelled": "Bestand lezen is geannuleerd.", "FileNotFound": "Bestand niet gevonden.", @@ -35,25 +35,27 @@ "HeaderConfirmation": "Bevestiging", "MessageKeyUpdated": "Dank u. Uw supporter sleutel is bijgewerkt.", "MessageKeyRemoved": "Dank u. Uw supporter sleutel is verwijderd.", + "TitleLiveTV": "Live TV", + "TitleSync": "Synchroniseer", "ErrorLaunchingChromecast": "Er is een fout opgetreden bij het starten van chromecast. Zorg ervoor dat uw apparaat is aangesloten op uw draadloze netwerk.", - "MessageErrorLoadingSupporterInfo": "There was an error loading supporter information. Please try again later.", - "MessageLinkYourSupporterKey": "Link your supporter key with up to {0} Emby Connect members to enjoy free access to the following apps:", - "HeaderConfirmRemoveUser": "Remove User", - "MessageSwipeDownOnRemoteControl": "Welcome to remote control. Swipe down anywhere on this screen to go back to where you came from.", - "MessageConfirmRemoveConnectSupporter": "Are you sure you wish to remove additional supporter benefits from this user?", + "MessageErrorLoadingSupporterInfo": "Er is een fout opgetreden bij het laden van uw supporter informatie. Probeer het later opnieuw.", + "MessageLinkYourSupporterKey": "Koppel uw supporters sleutel met maximaal {0} Emby Connect leden om te genieten van gratis toegang tot de volgende apps:", + "HeaderConfirmRemoveUser": "Gebruiker verwijderen", + "MessageSwipeDownOnRemoteControl": "Welkom bij de bediening op afstand. Veeg naar beneden ergens in dit scherm om terug te gaan naar waar je vandaan komt.", + "MessageConfirmRemoveConnectSupporter": "Bent u zeker dat u de extra supporter voordelen van deze gebruiker wilt verwijderen?", "ValueTimeLimitSingleHour": "Tijdslimiet: 1 uur", "ValueTimeLimitMultiHour": "Tijdslimiet: {0} uren", "HeaderUsers": "Gebruikers", - "PluginCategoryGeneral": "General", - "PluginCategoryContentProvider": "Content Providers", - "PluginCategoryScreenSaver": "Screen Savers", - "PluginCategoryTheme": "Themes", + "PluginCategoryGeneral": "Algemeen", + "PluginCategoryContentProvider": "Inhouds Providers", + "PluginCategoryScreenSaver": "Scherm beveiligers", + "PluginCategoryTheme": "Thema's", "PluginCategorySync": "Sync", - "PluginCategorySocialIntegration": "Social Networks", - "PluginCategoryNotifications": "Notifications", + "PluginCategorySocialIntegration": "Sociale Netwerken", + "PluginCategoryNotifications": "Meldingen", "PluginCategoryMetadata": "Metadata", "PluginCategoryLiveTV": "Live TV", - "PluginCategoryChannel": "Channels", + "PluginCategoryChannel": "Kanalen", "HeaderSearch": "Zoeken", "ValueDateCreated": "Datum aangemaakt: {0}", "LabelArtist": "Artiest", @@ -89,11 +91,10 @@ "ConfirmMessageScheduledTaskButton": "Deze operatie loopt normaal gesproken automatisch als een geplande taak. Het kan hier ook handmatig worden uitgevoerd. Om de geplande taak te configureren, zie:", "HeaderSupporterBenefit": "Een supporter lidmaatschap biedt voordelen zoals toegang tot synchronisatie, premium plug-ins, internet kanalen en meer. {0}Meer weten{1}.", "LabelSyncNoTargetsHelp": "Het lijkt erop dat je momenteel geen apps hebt die synchroniseren ondersteunen.", - "HeaderWelcomeToProjectServerDashboard": "Welcome to the Emby Server Dashboard", - "HeaderWelcomeToProjectWebClient": "Welcome to the Emby Web Client", + "HeaderWelcomeToProjectServerDashboard": "Welkom bij het Emby Server Dashboard", + "HeaderWelcomeToProjectWebClient": "Welkom bij de Emby Web Client", "ButtonTakeTheTour": "Volg de tour", "HeaderWelcomeBack": "Welkom terug!", - "TitleSync": "Synchroniseer", "TitlePlugins": "Plugins", "ButtonTakeTheTourToSeeWhatsNew": "Volg de tour om te zien wat nieuw is", "MessageNoSyncJobsFound": "Geen sync opdrachten gevonden. Maak sync opdrachten via de Synchronisatie knoppen in de web interface.", @@ -115,10 +116,11 @@ "LabelVersionInstalled": "{0} ge\u00efnstalleerd", "LabelNumberReviews": "{0} Recensies", "LabelFree": "Gratis", - "HeaderPlaybackError": "Playback Error", - "MessagePlaybackErrorNotAllowed": "You're currently not authorized to play this content. Please contact your system administrator for details.", - "MessagePlaybackErrorNoCompatibleStream": "No compatible streams are currently available. Please try again later.", + "HeaderPlaybackError": "Afspeel Fout", + "MessagePlaybackErrorNotAllowed": "U bent niet bevoegd om deze content af te spelen. Neem contact op met uw systeembeheerder voor meer informatie.", + "MessagePlaybackErrorNoCompatibleStream": "Geen compatibele streams beschikbaar. Probeer het later opnieuw.", "MessagePlaybackErrorRateLimitExceeded": "Your playback rate limit has been exceeded. Please contact your system administrator for details.", + "MessagePlaybackErrorPlaceHolder": "The content chosen is not playable from this device.", "HeaderSelectAudio": "Selecteer Audio", "HeaderSelectSubtitles": "Selecteer Ondertitels", "ButtonMarkForRemoval": "Van apparaat verwijderen", @@ -192,7 +194,7 @@ "MessagePleaseSelectOneItem": "Selecteer ten minste een item.", "MessagePleaseSelectTwoItems": "Selecteer ten minste twee items.", "MessageTheFollowingItemsWillBeGrouped": "De volgende titels worden gegroepeerd in \u00e9\u00e9n item:", - "MessageConfirmItemGrouping": "Emby apps will automatically choose the optimal version to play based on device and network performance. Are you sure you wish to continue?", + "MessageConfirmItemGrouping": "Emby apps zullen automatisch de optimale versie kiezen om afspelen op basis van het apparaat en de prestaties van het netwerk. Weet u zeker dat u door wilt gaan?", "HeaderResume": "Hervatten", "HeaderMyViews": "Mijn Overzichten", "HeaderLibraryFolders": "Media Mappen", @@ -240,8 +242,8 @@ "OrganizePatternResult": "Resultaat: {0}", "HeaderRestart": "Herstart", "HeaderShutdown": "Afsluiten", - "MessageConfirmRestart": "Are you sure you wish to restart Emby Server?", - "MessageConfirmShutdown": "Are you sure you wish to shutdown Emby Server?", + "MessageConfirmRestart": "Weet u zeker dat u Emby Server wilt herstarten?", + "MessageConfirmShutdown": "Weet u zeker dat u Emby Server wilt afsluiten?", "ButtonUpdateNow": "Nu bijwerken", "ValueItemCount": "{0} item", "ValueItemCountPlural": "{0} items", @@ -310,7 +312,7 @@ "OptionBlockLiveTvChannels": "Live TV Kanalen", "OptionBlockChannelContent": "Internet kanaal Inhoud", "ButtonRevoke": "Herroepen", - "MessageConfirmRevokeApiKey": "Are you sure you wish to revoke this api key? The application's connection to Emby Server will be abruptly terminated.", + "MessageConfirmRevokeApiKey": "Weet je zeker dat je deze api key in wilt trekken? De verbinding met Emby Server zal direct afgebroken worden.", "HeaderConfirmRevokeApiKey": "Intrekken Api Sleutel", "ValueContainer": "Container: {0}", "ValueAudioCodec": "Audio Codec: {0}", @@ -529,12 +531,12 @@ "MessageYouHaveALifetimeMembership": "You have a lifetime supporter membership. You can provide additional donations on a one-time or recurring basis using the options below. Thank you for supporting Emby.", "MessageYouHaveAnActiveRecurringMembership": "U hebt een actief {0} lidmaatschap. U kunt met de opties hieronder uw lidmaatschap upgraden.", "ButtonDelete": "Verwijderen", - "HeaderEmbyAccountAdded": "Emby Account Added", - "MessageEmbyAccountAdded": "The Emby account has been added to this user.", - "MessagePendingEmbyAccountAdded": "The Emby account has been added to this user. An email will be sent to the owner of the account. The invitation will need to be confirmed by clicking a link within the email.", - "HeaderEmbyAccountRemoved": "Emby Account Removed", - "MessageEmbyAccontRemoved": "The Emby account has been removed from this user.", - "TooltipLinkedToEmbyConnect": "Linked to Emby Connect", + "HeaderEmbyAccountAdded": "Emby Account Toegevoegd", + "MessageEmbyAccountAdded": "Het Emby account is aan deze gebruiker toegevoegd.", + "MessagePendingEmbyAccountAdded": "Het Emby account is aan deze gebruiker toegevoegd.Er wordt een emailbericht naar de eigenaar van het account gestuurd. De uitnodigind moet bevestigd worden door op de link in het emailbericht te klikken.", + "HeaderEmbyAccountRemoved": "Emby Account Verwijderd", + "MessageEmbyAccontRemoved": "Het Emby account is verwijderd van deze gebruiker.", + "TooltipLinkedToEmbyConnect": "Gekoppeld aan Emby Connect", "HeaderUnrated": "Geen rating", "ValueDiscNumber": "Disc {0}", "HeaderUnknownDate": "Onbekende datum", @@ -697,7 +699,7 @@ "ButtonParentalControl": "Ouderlijk toezicht", "HeaderInvitationSent": "Uitnodiging verzonden", "MessageInvitationSentToUser": "Een email is verzonden naar {0} om uw uitnodiging om media te delen te accepteren.", - "MessageInvitationSentToNewUser": "An email has been sent to {0} inviting them to sign up with Emby.", + "MessageInvitationSentToNewUser": "Een email is verzonden naar {0} met een uitnodiging om aan te melden bij Emby.", "HeaderConnectionFailure": "Verbindingsfout", "MessageUnableToConnectToServer": "Het is momenteel niet mogelijk met de geselecteerde server te verbinden. Controleer dat deze draait en probeer het opnieuw.", "ButtonSelectServer": "Selecteer server", @@ -714,7 +716,7 @@ "MessageInvalidForgotPasswordPin": "Er is een ongeldige of verlopen pincode ingegeven. Probeer opnieuw.", "MessagePasswordResetForUsers": "Wachtwoorden zijn verwijderd van de volgende gebruikers:", "HeaderInviteGuest": "Nodig gast uit", - "ButtonLinkMyEmbyAccount": "Link my account now", + "ButtonLinkMyEmbyAccount": "Koppel mijn account nu", "MessageConnectAccountRequiredToInviteGuest": "In order to invite guests you need to first link your Emby account to this server.", "ButtonSync": "Synchronisatie", "SyncMedia": "Synchroniseer media", @@ -743,6 +745,6 @@ "SyncJobItemStatusFailed": "Mislukt", "SyncJobItemStatusRemovedFromDevice": "Van apparaat verwijderd", "SyncJobItemStatusCancelled": "Geannuleerd", - "LabelProfile": "Profile:", + "LabelProfile": "profiel:", "LabelBitrateMbps": "Bitrate (Mbps):" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/pl.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/pl.json index e0966eabb3..da54b8c1da 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/pl.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/pl.json @@ -35,6 +35,8 @@ "HeaderConfirmation": "Confirmation", "MessageKeyUpdated": "Thank you. Your supporter key has been updated.", "MessageKeyRemoved": "Thank you. Your supporter key has been removed.", + "TitleLiveTV": "Live TV", + "TitleSync": "Sync", "ErrorLaunchingChromecast": "There was an error launching chromecast. Please ensure your device is connected to your wireless network.", "MessageErrorLoadingSupporterInfo": "There was an error loading supporter information. Please try again later.", "MessageLinkYourSupporterKey": "Link your supporter key with up to {0} Emby Connect members to enjoy free access to the following apps:", @@ -93,7 +95,6 @@ "HeaderWelcomeToProjectWebClient": "Welcome to the Emby Web Client", "ButtonTakeTheTour": "Take the tour", "HeaderWelcomeBack": "Welcome back!", - "TitleSync": "Sync", "TitlePlugins": "Plugins", "ButtonTakeTheTourToSeeWhatsNew": "Take the tour to see what's new", "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.", @@ -119,6 +120,7 @@ "MessagePlaybackErrorNotAllowed": "You're currently not authorized to play this content. Please contact your system administrator for details.", "MessagePlaybackErrorNoCompatibleStream": "No compatible streams are currently available. Please try again later.", "MessagePlaybackErrorRateLimitExceeded": "Your playback rate limit has been exceeded. Please contact your system administrator for details.", + "MessagePlaybackErrorPlaceHolder": "The content chosen is not playable from this device.", "HeaderSelectAudio": "Select Audio", "HeaderSelectSubtitles": "Select Subtitles", "ButtonMarkForRemoval": "Remove from device", diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_BR.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_BR.json index 7cd4133ad6..0f7e35454c 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_BR.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_BR.json @@ -35,6 +35,8 @@ "HeaderConfirmation": "Confirma\u00e7\u00e3o", "MessageKeyUpdated": "Obrigado. Sua chave de colaborador foi atualizada.", "MessageKeyRemoved": "Obrigado. Sua chave de colaborador foi removida.", + "TitleLiveTV": "TV ao Vivo", + "TitleSync": "Sinc", "ErrorLaunchingChromecast": "Ocorreu um erro ao iniciar o chromecast. Por favor verifique se seu dispositivo est\u00e1 conectado \u00e0 sua rede sem fio.", "MessageErrorLoadingSupporterInfo": "Ocorreu um erro ao carregar a informa\u00e7\u00e3o do colaborador. Por favor, tente novamente mais tarde.", "MessageLinkYourSupporterKey": "Associe sua chave de colaborador com at\u00e9 {0} membros do Emby Connect para desfrutar acesso gr\u00e1tis \u00e0s seguintes apps:", @@ -93,7 +95,6 @@ "HeaderWelcomeToProjectWebClient": "Bem vindo ao Emby Web Client", "ButtonTakeTheTour": "Fa\u00e7a o tour", "HeaderWelcomeBack": "Bem-vindo novamente!", - "TitleSync": "Sinc", "TitlePlugins": "Plugins", "ButtonTakeTheTourToSeeWhatsNew": "Fa\u00e7a o tour para ver as novidades", "MessageNoSyncJobsFound": "Nenhuma tarefa de sincroniza\u00e7\u00e3o encontrada. Crie uma tarefa de sincroniza\u00e7\u00e3o usando os bot\u00f5es Sincroniza\u00e7\u00e3o encontrados na interface web.", @@ -119,6 +120,7 @@ "MessagePlaybackErrorNotAllowed": "Voc\u00ea n\u00e3o est\u00e1 autorizado a reproduzir este conte\u00fado. Por favor, entre em contato com o administrador do sistema para mais detalhes.", "MessagePlaybackErrorNoCompatibleStream": "N\u00e3o existem streams compat\u00edveis dispon\u00edveis. Por favor, tente novamente mais tarde.", "MessagePlaybackErrorRateLimitExceeded": "Seu limite da taxa de reprodu\u00e7\u00e3o foi excedido. Por favor, entre em contato com o administrador do sistema para mais detalhes.", + "MessagePlaybackErrorPlaceHolder": "O conte\u00fado escolhido n\u00e3o \u00e9 reproduz\u00edvel neste dispositivo.", "HeaderSelectAudio": "Selecione \u00c1udio", "HeaderSelectSubtitles": "Selecione Legendas", "ButtonMarkForRemoval": "Remover do dispositivo", @@ -448,8 +450,8 @@ "OptionHomeVideos": "V\u00eddeos caseiros", "OptionBooks": "Livros", "OptionAdultVideos": "V\u00eddeos adultos", - "ButtonUp": "Para cima", - "ButtonDown": "Para baixo", + "ButtonUp": "Subir", + "ButtonDown": "Descer", "LabelMetadataReaders": "Leitores de metadados:", "LabelMetadataReadersHelp": "Classifique por ordem de prioridade suas fontes de metadados locais preferidas. O primeiro arquivo encontrado ser\u00e1 lido.", "LabelMetadataDownloaders": "Downloaders de metadados:", @@ -493,7 +495,7 @@ "HeaderMediaLocations": "Localiza\u00e7\u00f5es de M\u00eddia", "LabelContentTypeValue": "Tipo de conte\u00fado: {0}", "LabelPathSubstitutionHelp": "Opcional: Substitui\u00e7\u00e3o de caminho pode mapear caminhos do servidor para compartilhamentos de rede de forma a que os clientes possam acessar para reprodu\u00e7\u00e3o direta.", - "FolderTypeUnset": "Indefinida (conte\u00fado misto)", + "FolderTypeUnset": "Indefinido (conte\u00fado misto)", "FolderTypeMovies": "Filmes", "FolderTypeMusic": "M\u00fasica", "FolderTypeAdultVideos": "V\u00eddeos adultos", @@ -670,29 +672,29 @@ "WebClientTourUserPreferences4": "Configure imagens de fundo, m\u00fasicas-tema e reprodutores externos", "WebClientTourMobile1": "O cliente web funciona perfeitamente em smartphones e tablets...", "WebClientTourMobile2": "E controle facilmente outros dispositivos e apps do Emby", - "WebClientTourMySync": "Sincronize sua m\u00eddia pessoal para seus dispositivos para assisti-la off-line.", + "WebClientTourMySync": "Sincronize sua m\u00eddia pessoal para seus dispositivos para assistir off-line.", "MessageEnjoyYourStay": "Divirta-se", "DashboardTourDashboard": "O painel do servidor permite monitorar seu servidor e seus usu\u00e1rios. Voc\u00ea sempre poder\u00e1 saber quem est\u00e1 fazendo o qu\u00ea e onde est\u00e3o.", "DashboardTourHelp": "A ajuda dentro da app fornece bot\u00f5es para abrir p\u00e1ginas wiki relacionadas ao conte\u00fado na tela.", "DashboardTourUsers": "Crie facilmente contas de usu\u00e1rios para seus amigos e fam\u00edlia, cada um com sua permiss\u00e3o, acesso \u00e0 biblioteca, controle parental e mais.", "DashboardTourCinemaMode": "O modo cinema traz a experi\u00eancia do cinema para sua sala, permitindo reproduzir trailers e intros personalizadas antes da fun\u00e7\u00e3o principal.", - "DashboardTourChapters": "Ative a gera\u00e7\u00e3o de imagem do cap\u00edtulo para seus v\u00eddeos para ter uma apresenta\u00e7\u00e3o mais prazeirosa.", + "DashboardTourChapters": "Ative a gera\u00e7\u00e3o de imagem dos cap\u00edtulos de seus v\u00eddeos para ter uma apresenta\u00e7\u00e3o mais prazeirosa.", "DashboardTourSubtitles": "Fa\u00e7a download de legendas para os seus v\u00eddeos, em qualquer idioma, automaticamente.", "DashboardTourPlugins": "Instale plugins, como os canais de v\u00eddeo de internet, tv ao vivo, rastreadores de metadados e mais.", "DashboardTourNotifications": "Envie, automaticamente, notifica\u00e7\u00f5es de eventos do servidor para seus dispositivos m\u00f3veis, email e mais.", "DashboardTourScheduledTasks": "Gerencie facilmente opera\u00e7\u00f5es longas com tarefas agendadas. Decida quando executar e com que frequ\u00eancia.", "DashboardTourMobile": "O painel do Servidor Emby funciona perfeitamente em smartphones e tablets. Gerencie seu servidor da palma de sua m\u00e3o a qualquer hora, em qualquer lugar.", - "DashboardTourSync": "Sincronize sua m\u00eddia pessoal para seus dispositivos para assisti-la off-line.", + "DashboardTourSync": "Sincronize sua m\u00eddia pessoal para seus dispositivos para assistir off-line.", "MessageRefreshQueued": "Atualiza\u00e7\u00e3o iniciada", "TabDevices": "Dispositivos", "TabExtras": "Extras", "DeviceLastUsedByUserName": "Utilizado por \u00faltimo por {0}", "HeaderDeleteDevice": "Excluir Dispositivo", - "DeleteDeviceConfirmation": "Tem certeza de que deseja excluir este dispositivo? Ele reaparecer\u00e1 a pr\u00f3xima vez que um usu\u00e1rio utiliz\u00e1-lo.", + "DeleteDeviceConfirmation": "Deseja realmente excluir este dispositivo? Ele reaparecer\u00e1 da pr\u00f3xima vez que um usu\u00e1rio utiliz\u00e1-lo.", "LabelEnableCameraUploadFor": "Habilitar envio atrav\u00e9s de c\u00e2mera para:", - "HeaderSelectUploadPath": "Selecione o caminho para carga", + "HeaderSelectUploadPath": "Selecionar o Caminho para Upload", "LabelEnableCameraUploadForHelp": "Os uploads ocorrer\u00e3o automaticamente em segundo plano quando entrar no Emby.", - "ErrorMessageStartHourGreaterThanEnd": "O tempo final deve ser maior que o tempo inicial.", + "ErrorMessageStartHourGreaterThanEnd": "A hora final deve ser maior que a hora inicial.", "ButtonLibraryAccess": "Acesso \u00e0 biblioteca", "ButtonParentalControl": "Controle Parental", "HeaderInvitationSent": "Convite Enviado", diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_PT.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_PT.json index 2e2f1c8874..6a1092c898 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_PT.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_PT.json @@ -35,6 +35,8 @@ "HeaderConfirmation": "Confirmation", "MessageKeyUpdated": "Thank you. Your supporter key has been updated.", "MessageKeyRemoved": "Thank you. Your supporter key has been removed.", + "TitleLiveTV": "TV ao Vivo", + "TitleSync": "Sync", "ErrorLaunchingChromecast": "There was an error launching chromecast. Please ensure your device is connected to your wireless network.", "MessageErrorLoadingSupporterInfo": "There was an error loading supporter information. Please try again later.", "MessageLinkYourSupporterKey": "Link your supporter key with up to {0} Emby Connect members to enjoy free access to the following apps:", @@ -93,7 +95,6 @@ "HeaderWelcomeToProjectWebClient": "Welcome to the Emby Web Client", "ButtonTakeTheTour": "Fa\u00e7a o tour", "HeaderWelcomeBack": "Welcome back!", - "TitleSync": "Sync", "TitlePlugins": "Plugins", "ButtonTakeTheTourToSeeWhatsNew": "Fa\u00e7a o tour para ver as novidades", "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.", @@ -119,6 +120,7 @@ "MessagePlaybackErrorNotAllowed": "You're currently not authorized to play this content. Please contact your system administrator for details.", "MessagePlaybackErrorNoCompatibleStream": "No compatible streams are currently available. Please try again later.", "MessagePlaybackErrorRateLimitExceeded": "Your playback rate limit has been exceeded. Please contact your system administrator for details.", + "MessagePlaybackErrorPlaceHolder": "The content chosen is not playable from this device.", "HeaderSelectAudio": "Select Audio", "HeaderSelectSubtitles": "Select Subtitles", "ButtonMarkForRemoval": "Remove from device", diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/ru.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/ru.json index 7616dd7cb1..4718e89305 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/ru.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/ru.json @@ -35,6 +35,8 @@ "HeaderConfirmation": "\u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435", "MessageKeyUpdated": "\u041a\u043b\u044e\u0447 \u0441\u043f\u043e\u043d\u0441\u043e\u0440\u0430 \u0431\u044b\u043b \u043e\u0431\u043d\u043e\u0432\u043b\u0451\u043d.", "MessageKeyRemoved": "\u041a\u043b\u044e\u0447 \u0441\u043f\u043e\u043d\u0441\u043e\u0440\u0430 \u0431\u044b\u043b \u0443\u0434\u0430\u043b\u0451\u043d.", + "TitleLiveTV": "\u0422\u0412-\u044d\u0444\u0438\u0440", + "TitleSync": "\u0421\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u044f", "ErrorLaunchingChromecast": "\u041f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0435 Chromecast. \u0423\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u0432\u0430\u0448\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043e \u043a \u0431\u0435\u0441\u043f\u0440\u043e\u0432\u043e\u0434\u043d\u043e\u0439 \u0441\u0435\u0442\u0438.", "MessageErrorLoadingSupporterInfo": "\u041f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e \u0441\u043f\u043e\u043d\u0441\u043e\u0440\u0435. \u041f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443 \u043f\u043e\u0437\u0436\u0435.", "MessageLinkYourSupporterKey": "\u0421\u0432\u044f\u0436\u0438\u0442\u0435 \u0432\u0430\u0448 \u043a\u043b\u044e\u0447 \u0441\u043f\u043e\u043d\u0441\u043e\u0440\u0430 \u0441\u043e \u0432\u043f\u043b\u043e\u0442\u044c \u0434\u043e {0} \u0447\u043b\u0435\u043d\u043e\u0432 Emby Connect, \u0447\u0442\u043e\u0431\u044b \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0431\u0435\u0441\u043f\u043b\u0430\u0442\u043d\u044b\u043c \u0434\u043e\u0441\u0442\u0443\u043f\u043e\u043c \u043a \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f\u043c:", @@ -93,12 +95,11 @@ "HeaderWelcomeToProjectWebClient": "\u0412\u0435\u0431-\u043a\u043b\u0438\u0435\u043d\u0442 Emby \u043f\u0440\u0438\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0432\u0430\u0441!", "ButtonTakeTheTour": "\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u0438\u0442\u044c\u0441\u044f", "HeaderWelcomeBack": "\u0421 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0435\u043d\u0438\u0435\u043c!", - "TitleSync": "\u0421\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u044f", "TitlePlugins": "\u041f\u043b\u0430\u0433\u0438\u043d\u044b", "ButtonTakeTheTourToSeeWhatsNew": "\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u0438\u0442\u044c\u0441\u044f \u0441 \u043d\u043e\u0432\u0430\u0446\u0438\u044f\u043c\u0438", "MessageNoSyncJobsFound": "\u0417\u0430\u0434\u0430\u043d\u0438\u0439 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e. \u0421\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u0437\u0430\u0434\u0430\u043d\u0438\u0435 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043a\u043d\u043e\u043f\u043e\u043a \u0421\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c, \u043d\u0430\u0445\u043e\u0434\u044f\u0449\u0438\u0445\u0441\u044f \u043f\u043e \u0432\u0441\u0435\u043c\u0443 \u0432\u0435\u0431-\u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0443.", "HeaderLibraryAccess": "\u0414\u043e\u0441\u0442\u0443\u043f \u043a \u043c\u0435\u0434\u0438\u0430\u0442\u0435\u043a\u0435", - "HeaderChannelAccess": "\u0414\u043e\u0441\u0442\u0443\u043f \u043a \u043a\u0430\u043d\u0430\u043b\u0430\u043c", + "HeaderChannelAccess": "\u0414\u043e\u0441\u0442\u0443\u043f \u043a\u043e \u043a\u0430\u043d\u0430\u043b\u0430\u043c", "HeaderDeviceAccess": "\u0414\u043e\u0441\u0442\u0443\u043f \u0441 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430", "HeaderSelectDevices": "\u0412\u044b\u0431\u043e\u0440 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430", "ButtonCancelItem": "\u041e\u0442\u043c\u0435\u043d\u0438\u0442\u044c \u044d\u043b\u0435\u043c\u0435\u043d\u0442", @@ -119,6 +120,7 @@ "MessagePlaybackErrorNotAllowed": "\u0412 \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0438\u0439 \u043c\u043e\u043c\u0435\u043d\u0442 \u0432\u044b \u043d\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u044b \u0434\u043b\u044f \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0430 \u0434\u0430\u043d\u043d\u043e\u0433\u043e \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u044f. \u0414\u043b\u044f \u0434\u0435\u0442\u0430\u043b\u0435\u0439 \u0441\u0432\u044f\u0436\u0438\u0442\u0435\u0441\u044c \u0441 \u0432\u0430\u0448\u0438\u043c \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u044b\u043c \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440\u043e\u043c.", "MessagePlaybackErrorNoCompatibleStream": "\u0412 \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0438\u0439 \u043c\u043e\u043c\u0435\u043d\u0442 \u043d\u0435\u0442 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0445 \u0441\u043e\u0432\u043c\u0435\u0441\u0442\u0438\u043c\u044b\u0445 \u043f\u043e\u0442\u043e\u043a\u043e\u0432. \u041f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443 \u043f\u043e\u0437\u0436\u0435.", "MessagePlaybackErrorRateLimitExceeded": "\u0412\u0430\u0448\u0430 \u043f\u0440\u0435\u0434\u0435\u043b\u044c\u043d\u0430\u044f \u043f\u043e\u0442\u043e\u043a\u043e\u0432\u0430\u044f \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u044c \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u044f \u0431\u044b\u043b\u0430 \u043f\u0440\u0435\u0432\u044b\u0448\u0435\u043d\u0430. \u0414\u043b\u044f \u0434\u0435\u0442\u0430\u043b\u0435\u0439 \u0441\u0432\u044f\u0436\u0438\u0442\u0435\u0441\u044c \u0441 \u0432\u0430\u0448\u0438\u043c \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u044b\u043c \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440\u043e\u043c.", + "MessagePlaybackErrorPlaceHolder": "\u0412\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435 \u043d\u0435 \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0441\u044f \u0441 \u0434\u0430\u043d\u043d\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430.", "HeaderSelectAudio": "\u0412\u044b\u0431\u043e\u0440 \u0430\u0443\u0434\u0438\u043e", "HeaderSelectSubtitles": "\u0412\u044b\u0431\u043e\u0440 \u0441\u0443\u0431\u0442\u0438\u0442\u0440\u043e\u0432", "ButtonMarkForRemoval": "\u0418\u0437\u044a\u044f\u0442\u044c \u0441 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430", @@ -396,12 +398,12 @@ "ButtonAudioTracks": "\u041a \u0430\u0443\u0434\u0438\u043e\u0434\u043e\u0440\u043e\u0436\u043a\u0430\u043c", "ButtonSubtitles": "\u041a \u0441\u0443\u0431\u0442\u0438\u0442\u0440\u0430\u043c", "ButtonScenes": "\u041a \u0441\u0446\u0435\u043d\u0430\u043c", - "ButtonQuality": "\u041a \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0443", + "ButtonQuality": "\u041a\u043e \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0443", "HeaderNotifications": "\u0423\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f", "HeaderSelectPlayer": "\u0412\u044b\u0431\u043e\u0440 \u043f\u0440\u043e\u0438\u0433\u0440\u044b\u0432\u0430\u0442\u0435\u043b\u044f:", "ButtonSelect": "\u0412\u044b\u0431\u0440\u0430\u0442\u044c", "ButtonNew": "\u0421\u043e\u0437\u0434\u0430\u0442\u044c", - "MessageInternetExplorerWebm": "\u0414\u043b\u044f \u0434\u043e\u0441\u0442\u0438\u0436\u0435\u043d\u0438\u044f \u043d\u0430\u0438\u043b\u0443\u0447\u0448\u0438\u0445 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432 \u0432 Internet Explorer, \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0435 \u043f\u043b\u0430\u0433\u0438\u043d \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u044f WebM.", + "MessageInternetExplorerWebm": "\u0414\u043b\u044f \u043b\u0443\u0447\u0448\u0435\u0433\u043e \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430 \u0432 Internet Explorer, \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0435 \u043f\u043b\u0430\u0433\u0438\u043d \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u044f WebM.", "HeaderVideoError": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0432\u0438\u0434\u0435\u043e", "ButtonAddToPlaylist": "\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043a \u0441\u043f\u0438\u0441\u043a\u0443 \u0432\u043e\u0441\u043f\u0440-\u0438\u044f", "HeaderAddToPlaylist": "\u0414\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043a \u0441\u043f\u0438\u0441\u043a\u0443 \u0432\u043e\u0441\u043f\u0440-\u0438\u044f", @@ -430,7 +432,7 @@ "HeaderNetwork": "\u0422\u0435\u043b\u0435\u0441\u0435\u0442\u044c", "HeaderYear": "\u0413\u043e\u0434", "HeaderGameSystem": "\u0418\u0433\u0440\u043e\u0432\u0430\u044f \u0441\u0438\u0441\u0442\u0435\u043c\u0430", - "HeaderPlayers": "\u0418\u0433\u0440\u043e\u043a\u0438", + "HeaderPlayers": "\u041f\u0440\u043e\u0438\u0433\u0440\u044b\u0432\u0430\u0442\u0435\u043b\u0438", "HeaderEmbeddedImage": "\u0412\u043d\u0435\u0434\u0440\u0451\u043d\u043d\u044b\u0439 \u0440\u0438\u0441\u0443\u043d\u043e\u043a", "HeaderTrack": "\u0414\u043e\u0440\u043e\u0436\u043a\u0430", "HeaderDisc": "\u0414\u0438\u0441\u043a", @@ -675,7 +677,7 @@ "DashboardTourDashboard": "\u0421\u0435\u0440\u0432\u0435\u0440\u043d\u0430\u044f \u0418\u043d\u0444\u043e\u043f\u0430\u043d\u0435\u043b\u044c \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u043f\u043e \u043e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u043d\u0438\u044e \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439. \u0412\u044b \u0431\u0443\u0434\u0435\u0442\u0435 \u0432\u0441\u0435\u0433\u0434\u0430 \u0437\u043d\u0430\u0442\u044c, \u043a\u0442\u043e \u0437\u0430\u043d\u0438\u043c\u0430\u0435\u0442\u0441\u044f \u0447\u0435\u043c \u0438 \u0433\u0434\u0435 \u043e\u043d\u0438 \u043d\u0430\u0445\u043e\u0434\u044f\u0442\u0441\u044f.", "DashboardTourHelp": "\u0412\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u0430\u044f \u0441\u043f\u0440\u0430\u0432\u043a\u0430 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u043f\u0440\u043e\u0441\u0442\u044b\u0435 \u043a\u043d\u043e\u043f\u043a\u0438, \u0447\u0442\u043e\u0431\u044b \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0442\u044c \u0432\u0438\u043a\u0438-\u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b, \u043e\u0442\u043d\u043e\u0441\u044f\u0449\u0438\u0445\u0441\u044f \u043a \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u044e \u044d\u043a\u0440\u0430\u043d\u0430.", "DashboardTourUsers": "\u0421\u0432\u043e\u0431\u043e\u0434\u043d\u043e \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u0443\u0447\u0451\u0442\u043d\u044b\u0435 \u0437\u0430\u043f\u0438\u0441\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u0434\u043b\u044f \u0432\u0430\u0448\u0438\u0445 \u0434\u0440\u0443\u0437\u0435\u0439 \u0438 \u0447\u043b\u0435\u043d\u043e\u0432 \u0441\u0435\u043c\u044c\u0438, \u043a\u0430\u0436\u0434\u0443\u044e \u0441 \u0438\u0445 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u043c\u0438 \u043f\u0440\u0430\u0432\u0430\u043c\u0438, \u0434\u043e\u0441\u0442\u0443\u043f\u043e\u043c \u043a \u043c\u0435\u0434\u0438\u0430\u0442\u0435\u043a\u0435, \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435\u043c \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435\u043c \u0438 \u0442.\u0434.", - "DashboardTourCinemaMode": "\u0420\u0435\u0436\u0438\u043c \u043a\u0438\u043d\u043e\u0442\u0435\u0430\u0442\u0440\u0430 \u043f\u0440\u0438\u0432\u043d\u043e\u0441\u0438\u0442 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u043a\u0438\u043d\u043e\u0437\u0430\u043b\u0430 \u043f\u0440\u044f\u043c\u0438\u043a\u043e\u043c \u0432 \u0432\u0430\u0448\u0443 \u0433\u043e\u0441\u0442\u0438\u043d\u0443\u044e, \u0432\u043c\u0435\u0441\u0442\u0435 \u0441\u043e \u0441\u043f\u043e\u0441\u043e\u0431\u043d\u043e\u0441\u0442\u044c\u044e \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u044c \u0442\u0440\u0435\u0439\u043b\u0435\u0440\u044b \u0438 \u043d\u0435\u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0435 \u0437\u0430\u0441\u0442\u0430\u0432\u043a\u0438 \u043f\u0435\u0440\u0435\u0434 \u0433\u043b\u0430\u0432\u043d\u044b\u043c \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b\u043e\u043c.", + "DashboardTourCinemaMode": "\u0420\u0435\u0436\u0438\u043c \u043a\u0438\u043d\u043e\u0442\u0435\u0430\u0442\u0440\u0430 \u043f\u0440\u0438\u0432\u043d\u043e\u0441\u0438\u0442 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u043a\u0438\u043d\u043e\u0437\u0430\u043b\u0430 \u043f\u0440\u044f\u043c\u0438\u043a\u043e\u043c \u0432\u043e \u0432\u0430\u0448\u0443 \u0433\u043e\u0441\u0442\u0438\u043d\u0443\u044e, \u0432\u043c\u0435\u0441\u0442\u0435 \u0441\u043e \u0441\u043f\u043e\u0441\u043e\u0431\u043d\u043e\u0441\u0442\u044c\u044e \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u044c \u0442\u0440\u0435\u0439\u043b\u0435\u0440\u044b \u0438 \u043d\u0435\u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0435 \u0437\u0430\u0441\u0442\u0430\u0432\u043a\u0438 \u043f\u0435\u0440\u0435\u0434 \u0433\u043b\u0430\u0432\u043d\u044b\u043c \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b\u043e\u043c.", "DashboardTourChapters": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u0435 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0440\u0438\u0441\u0443\u043d\u043a\u043e\u0432 \u0441\u0446\u0435\u043d \u043a \u0432\u0438\u0434\u0435\u043e, \u0434\u043b\u044f \u0431\u043e\u043b\u0435\u0435 \u043f\u0440\u0438\u0432\u043b\u0435\u043a\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0439 \u043f\u0440\u0435\u0437\u0435\u043d\u0442\u0430\u0446\u0438\u0438 \u0432\u043e \u0432\u0440\u0435\u043c\u044f \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0430.", "DashboardTourSubtitles": "\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0439\u0442\u0435 \u0441\u0443\u0431\u0442\u0438\u0442\u0440\u044b \u0434\u043b\u044f \u0432\u0438\u0434\u0435\u043e \u043d\u0430 \u043b\u044e\u0431\u043e\u043c \u044f\u0437\u044b\u043a\u0435.", "DashboardTourPlugins": "\u0423\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0439\u0442\u0435 \u043f\u043b\u0430\u0433\u0438\u043d\u044b, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442-\u043a\u0430\u043d\u0430\u043b\u043e\u0432 \u0432\u0438\u0434\u0435\u043e, \u0422\u0412-\u044d\u0444\u0438\u0440\u0430, \u0441\u043a\u0430\u043d\u043d\u0435\u0440\u043e\u0432 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0445 \u0438 \u0442.\u043f.", @@ -689,12 +691,12 @@ "DeviceLastUsedByUserName": "\u041f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435: {0}", "HeaderDeleteDevice": "\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e", "DeleteDeviceConfirmation": "\u0412\u044b \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u0443\u0434\u0430\u043b\u0438\u0442\u044c \u0434\u0430\u043d\u043d\u043e\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e? \u041e\u043d\u043e \u043f\u043e\u044f\u0432\u0438\u0442\u0441\u044f \u0441\u043d\u043e\u0432\u0430 \u0432 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u0440\u0430\u0437, \u043a\u043e\u0433\u0434\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0432\u043e\u0439\u0434\u0451\u0442 \u0441 \u043d\u0435\u0433\u043e.", - "LabelEnableCameraUploadFor": "\u0412\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0441 \u043a\u0430\u043c\u0435\u0440\u044b \u0434\u043b\u044f:", - "HeaderSelectUploadPath": "\u0412\u044b\u0431\u043e\u0440 \u043f\u0443\u0442\u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c\u043e\u0433\u043e", - "LabelEnableCameraUploadForHelp": "\u041e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u043f\u0440\u043e\u0438\u0437\u043e\u0439\u0434\u0443\u0442 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0432 \u0444\u043e\u043d\u043e\u0432\u043e\u043c \u0440\u0435\u0436\u0438\u043c\u0435, \u043f\u0440\u0438 \u0432\u0445\u043e\u0434\u0435 \u0432 Emby.", + "LabelEnableCameraUploadFor": "\u0412\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u0432\u044b\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u043d\u0438\u044f \u0441 \u043a\u0430\u043c\u0435\u0440\u044b \u0434\u043b\u044f:", + "HeaderSelectUploadPath": "\u0412\u044b\u0431\u043e\u0440 \u043f\u0443\u0442\u0438 \u0432\u044b\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u0435\u043c\u043e\u0433\u043e", + "LabelEnableCameraUploadForHelp": "\u0412\u044b\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u043d\u0438\u044f \u043f\u0440\u043e\u0438\u0437\u043e\u0439\u0434\u0443\u0442 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0432 \u0444\u043e\u043d\u043e\u0432\u043e\u043c \u0440\u0435\u0436\u0438\u043c\u0435, \u043f\u0440\u0438 \u0432\u0445\u043e\u0434\u0435 \u0432 Emby.", "ErrorMessageStartHourGreaterThanEnd": "\u041a\u043e\u043d\u0435\u0447\u043d\u043e\u0435 \u0432\u0440\u0435\u043c\u044f \u0434\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u0442\u044c \u043f\u043e\u0437\u0436\u0435, \u0447\u0435\u043c \u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e\u0435 \u0432\u0440\u0435\u043c\u044f.", - "ButtonLibraryAccess": "\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u044c\u0441\u044f \u043a \u043c\u0435\u0434\u0438\u0430\u0442\u0435\u043a\u0435", - "ButtonParentalControl": "\u0423\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435\u043c", + "ButtonLibraryAccess": "\u041a \u0434\u043e\u0441\u0442\u0443\u043f\u0443 \u043a \u043c\u0435\u0434\u0438\u0430\u0442\u0435\u043a\u0435", + "ButtonParentalControl": "\u041a \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044e \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435\u043c", "HeaderInvitationSent": "\u041f\u0440\u0438\u0433\u043b\u0430\u0448\u0435\u043d\u0438\u0435 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043e", "MessageInvitationSentToUser": "\u042d-\u043f\u043e\u0447\u0442\u0430 \u0431\u044b\u043b\u0430 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0430 \u043a {0}, \u0441 \u043f\u0440\u0435\u0434\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u043c \u043f\u0440\u0438\u043d\u044f\u0442\u044c \u0432\u0430\u0448\u0435 \u043f\u0440\u0438\u0433\u043b\u0430\u0448\u0435\u043d\u0438\u0435 \u043a \u043e\u0431\u0449\u0435\u043c\u0443 \u0434\u043e\u0441\u0442\u0443\u043f\u0443.", "MessageInvitationSentToNewUser": "\u042d-\u043f\u043e\u0447\u0442\u0430 \u0431\u044b\u043b\u0430 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0430 \u043a {0}, \u0441 \u043f\u0440\u0435\u0434\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u043c \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0432 Emby.", @@ -707,7 +709,7 @@ "ButtonAccept": "\u041f\u0440\u0438\u043d\u044f\u0442\u044c", "ButtonReject": "\u041e\u0442\u043a\u043b\u043e\u043d\u0438\u0442\u044c", "HeaderForgotPassword": "\u0417\u0430\u0431\u044b\u043b\u0438 \u043f\u0430\u0440\u043e\u043b\u044c?", - "MessageContactAdminToResetPassword": "\u0421\u0432\u044f\u0436\u0438\u0442\u0435\u0441\u044c \u0441 \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u044b\u043c \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440\u043e\u043c, \u0447\u0442\u043e\u0431\u044b \u0441\u0431\u0440\u043e\u0441\u0438\u0442\u044c \u0432\u0430\u0448 \u043f\u0430\u0440\u043e\u043b\u044c.", + "MessageContactAdminToResetPassword": "\u0421\u0432\u044f\u0436\u0438\u0442\u0435\u0441\u044c \u0441\u043e \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u044b\u043c \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440\u043e\u043c, \u0447\u0442\u043e\u0431\u044b \u0441\u0431\u0440\u043e\u0441\u0438\u0442\u044c \u0432\u0430\u0448 \u043f\u0430\u0440\u043e\u043b\u044c.", "MessageForgotPasswordInNetworkRequired": "\u041f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443 \u0432 \u043f\u0440\u0435\u0434\u0435\u043b\u0430\u0445 \u0432\u0430\u0448\u0435\u0439 \u0434\u043e\u043c\u0430\u0448\u043d\u0435\u0439 \u0441\u0435\u0442\u0438, \u0447\u0442\u043e\u0431\u044b \u043d\u0430\u0447\u0430\u0442\u044c \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u0441\u0431\u0440\u043e\u0441\u0430 \u043f\u0430\u0440\u043e\u043b\u044f.", "MessageForgotPasswordFileCreated": "\u0421\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u0444\u0430\u0439\u043b \u0431\u044b\u043b \u0441\u043e\u0437\u0434\u0430\u043d \u043d\u0430 \u0432\u0430\u0448\u0435\u043c \u0441\u0435\u0440\u0432\u0435\u0440\u0435 \u0438 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u0438 \u043e \u0442\u043e\u043c, \u043a\u0430\u043a \u043f\u043e\u0441\u0442\u0443\u043f\u0438\u0442\u044c:", "MessageForgotPasswordFileExpiration": "\u0421\u0431\u0440\u043e\u0441 PIN-\u043a\u043e\u0434\u0430 \u0438\u0441\u0442\u0435\u043a\u0430\u0435\u0442 {0}.", diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/sl_SI.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/sl_SI.json index e4d233ea2e..16df937c94 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/sl_SI.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/sl_SI.json @@ -35,6 +35,8 @@ "HeaderConfirmation": "Confirmation", "MessageKeyUpdated": "Thank you. Your supporter key has been updated.", "MessageKeyRemoved": "Thank you. Your supporter key has been removed.", + "TitleLiveTV": "Live TV", + "TitleSync": "Sync", "ErrorLaunchingChromecast": "There was an error launching chromecast. Please ensure your device is connected to your wireless network.", "MessageErrorLoadingSupporterInfo": "There was an error loading supporter information. Please try again later.", "MessageLinkYourSupporterKey": "Link your supporter key with up to {0} Emby Connect members to enjoy free access to the following apps:", @@ -93,7 +95,6 @@ "HeaderWelcomeToProjectWebClient": "Welcome to the Emby Web Client", "ButtonTakeTheTour": "Take the tour", "HeaderWelcomeBack": "Welcome back!", - "TitleSync": "Sync", "TitlePlugins": "Plugins", "ButtonTakeTheTourToSeeWhatsNew": "Take the tour to see what's new", "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.", @@ -119,6 +120,7 @@ "MessagePlaybackErrorNotAllowed": "You're currently not authorized to play this content. Please contact your system administrator for details.", "MessagePlaybackErrorNoCompatibleStream": "No compatible streams are currently available. Please try again later.", "MessagePlaybackErrorRateLimitExceeded": "Your playback rate limit has been exceeded. Please contact your system administrator for details.", + "MessagePlaybackErrorPlaceHolder": "The content chosen is not playable from this device.", "HeaderSelectAudio": "Select Audio", "HeaderSelectSubtitles": "Select Subtitles", "ButtonMarkForRemoval": "Remove from device", diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/sv.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/sv.json index d5af744352..4588903079 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/sv.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/sv.json @@ -35,6 +35,8 @@ "HeaderConfirmation": "Bekr\u00e4ftelse", "MessageKeyUpdated": "Tack. Din donationskod har uppdaterats.", "MessageKeyRemoved": "Tack. Din donationskod har raderats.", + "TitleLiveTV": "Live-TV", + "TitleSync": "Sync", "ErrorLaunchingChromecast": "Det gick inte att starta Chromecast. Kontrollera att enheten \u00e4r ansluten till det tr\u00e5dl\u00f6sa n\u00e4tverket.", "MessageErrorLoadingSupporterInfo": "There was an error loading supporter information. Please try again later.", "MessageLinkYourSupporterKey": "Link your supporter key with up to {0} Emby Connect members to enjoy free access to the following apps:", @@ -93,7 +95,6 @@ "HeaderWelcomeToProjectWebClient": "Welcome to the Emby Web Client", "ButtonTakeTheTour": "Ta en rundtur", "HeaderWelcomeBack": "Welcome back!", - "TitleSync": "Sync", "TitlePlugins": "Plugins", "ButtonTakeTheTourToSeeWhatsNew": "Take the tour to see what's new", "MessageNoSyncJobsFound": "Inga synkjobb hittades. Skapa synkjobb med hj\u00e4lp av Synk-knapparna som finns i hela gr\u00e4nssnittet.", @@ -119,6 +120,7 @@ "MessagePlaybackErrorNotAllowed": "You're currently not authorized to play this content. Please contact your system administrator for details.", "MessagePlaybackErrorNoCompatibleStream": "No compatible streams are currently available. Please try again later.", "MessagePlaybackErrorRateLimitExceeded": "Your playback rate limit has been exceeded. Please contact your system administrator for details.", + "MessagePlaybackErrorPlaceHolder": "The content chosen is not playable from this device.", "HeaderSelectAudio": "V\u00e4lj ljudsp\u00e5r", "HeaderSelectSubtitles": "V\u00e4lj undertexter", "ButtonMarkForRemoval": "Remove from device", diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/tr.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/tr.json index 7a7ac7a5fa..2e2f58a772 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/tr.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/tr.json @@ -35,6 +35,8 @@ "HeaderConfirmation": "Confirmation", "MessageKeyUpdated": "Thank you. Your supporter key has been updated.", "MessageKeyRemoved": "Thank you. Your supporter key has been removed.", + "TitleLiveTV": "Canl\u0131 TV", + "TitleSync": "Sync", "ErrorLaunchingChromecast": "There was an error launching chromecast. Please ensure your device is connected to your wireless network.", "MessageErrorLoadingSupporterInfo": "There was an error loading supporter information. Please try again later.", "MessageLinkYourSupporterKey": "Link your supporter key with up to {0} Emby Connect members to enjoy free access to the following apps:", @@ -93,7 +95,6 @@ "HeaderWelcomeToProjectWebClient": "Welcome to the Emby Web Client", "ButtonTakeTheTour": "Take the tour", "HeaderWelcomeBack": "Welcome back!", - "TitleSync": "Sync", "TitlePlugins": "Plugins", "ButtonTakeTheTourToSeeWhatsNew": "Take the tour to see what's new", "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.", @@ -119,6 +120,7 @@ "MessagePlaybackErrorNotAllowed": "You're currently not authorized to play this content. Please contact your system administrator for details.", "MessagePlaybackErrorNoCompatibleStream": "No compatible streams are currently available. Please try again later.", "MessagePlaybackErrorRateLimitExceeded": "Your playback rate limit has been exceeded. Please contact your system administrator for details.", + "MessagePlaybackErrorPlaceHolder": "The content chosen is not playable from this device.", "HeaderSelectAudio": "Select Audio", "HeaderSelectSubtitles": "Select Subtitles", "ButtonMarkForRemoval": "Remove from device", diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/uk.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/uk.json index c7bdbe26e2..9a8f4b57a8 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/uk.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/uk.json @@ -35,6 +35,8 @@ "HeaderConfirmation": "Confirmation", "MessageKeyUpdated": "Thank you. Your supporter key has been updated.", "MessageKeyRemoved": "Thank you. Your supporter key has been removed.", + "TitleLiveTV": "Live TV", + "TitleSync": "Sync", "ErrorLaunchingChromecast": "There was an error launching chromecast. Please ensure your device is connected to your wireless network.", "MessageErrorLoadingSupporterInfo": "There was an error loading supporter information. Please try again later.", "MessageLinkYourSupporterKey": "Link your supporter key with up to {0} Emby Connect members to enjoy free access to the following apps:", @@ -93,7 +95,6 @@ "HeaderWelcomeToProjectWebClient": "Welcome to the Emby Web Client", "ButtonTakeTheTour": "Take the tour", "HeaderWelcomeBack": "Welcome back!", - "TitleSync": "Sync", "TitlePlugins": "Plugins", "ButtonTakeTheTourToSeeWhatsNew": "Take the tour to see what's new", "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.", @@ -119,6 +120,7 @@ "MessagePlaybackErrorNotAllowed": "You're currently not authorized to play this content. Please contact your system administrator for details.", "MessagePlaybackErrorNoCompatibleStream": "No compatible streams are currently available. Please try again later.", "MessagePlaybackErrorRateLimitExceeded": "Your playback rate limit has been exceeded. Please contact your system administrator for details.", + "MessagePlaybackErrorPlaceHolder": "The content chosen is not playable from this device.", "HeaderSelectAudio": "Select Audio", "HeaderSelectSubtitles": "Select Subtitles", "ButtonMarkForRemoval": "Remove from device", diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/vi.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/vi.json index b24f0a6c6f..71e3533297 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/vi.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/vi.json @@ -35,6 +35,8 @@ "HeaderConfirmation": "Confirmation", "MessageKeyUpdated": "Thank you. Your supporter key has been updated.", "MessageKeyRemoved": "Thank you. Your supporter key has been removed.", + "TitleLiveTV": "Live TV", + "TitleSync": "Sync", "ErrorLaunchingChromecast": "There was an error launching chromecast. Please ensure your device is connected to your wireless network.", "MessageErrorLoadingSupporterInfo": "There was an error loading supporter information. Please try again later.", "MessageLinkYourSupporterKey": "Link your supporter key with up to {0} Emby Connect members to enjoy free access to the following apps:", @@ -93,7 +95,6 @@ "HeaderWelcomeToProjectWebClient": "Welcome to the Emby Web Client", "ButtonTakeTheTour": "Take the tour", "HeaderWelcomeBack": "Welcome back!", - "TitleSync": "Sync", "TitlePlugins": "Plugins", "ButtonTakeTheTourToSeeWhatsNew": "Take the tour to see what's new", "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.", @@ -119,6 +120,7 @@ "MessagePlaybackErrorNotAllowed": "You're currently not authorized to play this content. Please contact your system administrator for details.", "MessagePlaybackErrorNoCompatibleStream": "No compatible streams are currently available. Please try again later.", "MessagePlaybackErrorRateLimitExceeded": "Your playback rate limit has been exceeded. Please contact your system administrator for details.", + "MessagePlaybackErrorPlaceHolder": "The content chosen is not playable from this device.", "HeaderSelectAudio": "Select Audio", "HeaderSelectSubtitles": "Select Subtitles", "ButtonMarkForRemoval": "Remove from device", diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/zh_CN.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/zh_CN.json index 57b5ef69df..bfa2b3e24d 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/zh_CN.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/zh_CN.json @@ -35,6 +35,8 @@ "HeaderConfirmation": "\u786e\u8ba4", "MessageKeyUpdated": "\u8c22\u8c22\u3002\u4f60\u7684\u652f\u6301\u8005\u5e8f\u53f7\u5df2\u66f4\u65b0\u3002", "MessageKeyRemoved": "\u8c22\u8c22\u3002\u4f60\u7684\u652f\u6301\u8005\u5e8f\u53f7\u5df2\u79fb\u9664\u3002", + "TitleLiveTV": "\u7535\u89c6\u76f4\u64ad", + "TitleSync": "\u540c\u6b65", "ErrorLaunchingChromecast": "\u542f\u52a8chromecast\u9047\u5230\u9519\u8bef\uff0c\u8bf7\u786e\u8ba4\u8bbe\u5907\u5df2\u7ecf\u8fde\u63a5\u5230\u4f60\u7684\u65e0\u7ebf\u7f51\u7edc\u3002", "MessageErrorLoadingSupporterInfo": "There was an error loading supporter information. Please try again later.", "MessageLinkYourSupporterKey": "Link your supporter key with up to {0} Emby Connect members to enjoy free access to the following apps:", @@ -93,7 +95,6 @@ "HeaderWelcomeToProjectWebClient": "Welcome to the Emby Web Client", "ButtonTakeTheTour": "\u8fdb\u884c\u53c2\u89c2", "HeaderWelcomeBack": "\u6b22\u8fce\u56de\u6765\uff01", - "TitleSync": "\u540c\u6b65", "TitlePlugins": "Plugins", "ButtonTakeTheTourToSeeWhatsNew": "\u8fdb\u884c\u53c2\u89c2\uff0c\u770b\u770b\u6709\u4ec0\u4e48\u65b0\u4e1c\u897f", "MessageNoSyncJobsFound": "\u6ca1\u6709\u53d1\u73b0\u540c\u6b65\u4f5c\u4e1a\u3002\u4f7f\u7528Web\u754c\u9762\u4e2d\u7684\u540c\u6b65\u6309\u94ae\u6765\u521b\u5efa\u540c\u6b65\u4f5c\u4e1a\u3002", @@ -119,6 +120,7 @@ "MessagePlaybackErrorNotAllowed": "You're currently not authorized to play this content. Please contact your system administrator for details.", "MessagePlaybackErrorNoCompatibleStream": "No compatible streams are currently available. Please try again later.", "MessagePlaybackErrorRateLimitExceeded": "Your playback rate limit has been exceeded. Please contact your system administrator for details.", + "MessagePlaybackErrorPlaceHolder": "The content chosen is not playable from this device.", "HeaderSelectAudio": "\u9009\u62e9\u97f3\u9891", "HeaderSelectSubtitles": "\u9009\u62e9\u5b57\u5e55", "ButtonMarkForRemoval": "Remove from device", diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/zh_TW.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/zh_TW.json index c5c3e1cc92..68437bb68b 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/zh_TW.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/zh_TW.json @@ -35,6 +35,8 @@ "HeaderConfirmation": "Confirmation", "MessageKeyUpdated": "Thank you. Your supporter key has been updated.", "MessageKeyRemoved": "Thank you. Your supporter key has been removed.", + "TitleLiveTV": "\u96fb\u8996\u529f\u80fd", + "TitleSync": "Sync", "ErrorLaunchingChromecast": "There was an error launching chromecast. Please ensure your device is connected to your wireless network.", "MessageErrorLoadingSupporterInfo": "There was an error loading supporter information. Please try again later.", "MessageLinkYourSupporterKey": "Link your supporter key with up to {0} Emby Connect members to enjoy free access to the following apps:", @@ -93,7 +95,6 @@ "HeaderWelcomeToProjectWebClient": "Welcome to the Emby Web Client", "ButtonTakeTheTour": "Take the tour", "HeaderWelcomeBack": "Welcome back!", - "TitleSync": "Sync", "TitlePlugins": "Plugins", "ButtonTakeTheTourToSeeWhatsNew": "Take the tour to see what's new", "MessageNoSyncJobsFound": "No sync jobs found. Create sync jobs using the Sync buttons found throughout the web interface.", @@ -119,6 +120,7 @@ "MessagePlaybackErrorNotAllowed": "You're currently not authorized to play this content. Please contact your system administrator for details.", "MessagePlaybackErrorNoCompatibleStream": "No compatible streams are currently available. Please try again later.", "MessagePlaybackErrorRateLimitExceeded": "Your playback rate limit has been exceeded. Please contact your system administrator for details.", + "MessagePlaybackErrorPlaceHolder": "The content chosen is not playable from this device.", "HeaderSelectAudio": "Select Audio", "HeaderSelectSubtitles": "Select Subtitles", "ButtonMarkForRemoval": "Remove from device", diff --git a/MediaBrowser.Server.Implementations/Localization/Server/ar.json b/MediaBrowser.Server.Implementations/Localization/Server/ar.json index a2cf83409c..61aaea0f8b 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/ar.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/ar.json @@ -1384,5 +1384,7 @@ "HeaderUpcomingPrograms": "Upcoming Programs", "ButtonMoreItems": "More...", "LabelShowLibraryTileNames": "Show library tile names", - "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page" + "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page", + "OptionEnableTranscodingThrottle": "Enable throttling", + "OptionEnableTranscodingThrottleHelp": "Throttling will automatically adjust transcoding speed in order to minimize server cpu utilization during playback." } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/bg_BG.json b/MediaBrowser.Server.Implementations/Localization/Server/bg_BG.json index 6d41d3621c..5831f3d101 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/bg_BG.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/bg_BG.json @@ -1384,5 +1384,7 @@ "HeaderUpcomingPrograms": "Upcoming Programs", "ButtonMoreItems": "More...", "LabelShowLibraryTileNames": "Show library tile names", - "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page" + "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page", + "OptionEnableTranscodingThrottle": "Enable throttling", + "OptionEnableTranscodingThrottleHelp": "Throttling will automatically adjust transcoding speed in order to minimize server cpu utilization during playback." } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/ca.json b/MediaBrowser.Server.Implementations/Localization/Server/ca.json index 540c782ad1..65f8df592c 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/ca.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/ca.json @@ -1384,5 +1384,7 @@ "HeaderUpcomingPrograms": "Upcoming Programs", "ButtonMoreItems": "More...", "LabelShowLibraryTileNames": "Show library tile names", - "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page" + "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page", + "OptionEnableTranscodingThrottle": "Enable throttling", + "OptionEnableTranscodingThrottleHelp": "Throttling will automatically adjust transcoding speed in order to minimize server cpu utilization during playback." } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/cs.json b/MediaBrowser.Server.Implementations/Localization/Server/cs.json index e1784be779..8d9ccff49a 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/cs.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/cs.json @@ -1384,5 +1384,7 @@ "HeaderUpcomingPrograms": "Upcoming Programs", "ButtonMoreItems": "More...", "LabelShowLibraryTileNames": "Show library tile names", - "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page" + "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page", + "OptionEnableTranscodingThrottle": "Enable throttling", + "OptionEnableTranscodingThrottleHelp": "Throttling will automatically adjust transcoding speed in order to minimize server cpu utilization during playback." } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/da.json b/MediaBrowser.Server.Implementations/Localization/Server/da.json index 5a5e8edc2c..0dda452890 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/da.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/da.json @@ -1384,5 +1384,7 @@ "HeaderUpcomingPrograms": "Upcoming Programs", "ButtonMoreItems": "More...", "LabelShowLibraryTileNames": "Show library tile names", - "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page" + "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page", + "OptionEnableTranscodingThrottle": "Enable throttling", + "OptionEnableTranscodingThrottleHelp": "Throttling will automatically adjust transcoding speed in order to minimize server cpu utilization during playback." } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/de.json b/MediaBrowser.Server.Implementations/Localization/Server/de.json index de6c05207d..d69bede295 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/de.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/de.json @@ -15,7 +15,7 @@ "LabelFinish": "Fertig", "LabelNext": "N\u00e4chstes", "LabelYoureDone": "Du bist fertig!", - "WelcomeToProject": "Welcome to Emby!", + "WelcomeToProject": "Willkommen bei Emby!", "ThisWizardWillGuideYou": "Dieser Assistent wird dich durch den Einrichtungsprozess f\u00fchren. Um zu beginnen, w\u00e4hle bitte deine bevorzugte Sprache.", "TellUsAboutYourself": "Sag uns etwas \u00fcber dich selbst", "ButtonQuickStartGuide": "Schnellstart Instruktionen", @@ -368,8 +368,8 @@ "LabelMetadataDownloadLanguage": "Bevorzugte Sprache f\u00fcr Downloads:", "ButtonAutoScroll": "Auto-scroll", "LabelImageSavingConvention": "Speicherconvention der Bilddatein:", - "LabelImageSavingConventionHelp": "Emby recognizes images from most major media applications. Choosing your downloading convention is useful if you also use other products.", - "OptionImageSavingCompatible": "Compatible - Emby\/Kodi\/Plex", + "LabelImageSavingConventionHelp": "Emby erkennt Bilder von den meisten bekannten Media-Anwendungen. Eine Auswahl des Download-Formates ist sinnvoll wenn Sie auch andere Produkte verwenden.", + "OptionImageSavingCompatible": "Kompatibel - Emby\/ Kodi\/ Plex", "OptionImageSavingStandard": "Standard - MB2", "ButtonSignIn": "Einloggen", "TitleSignIn": "Einloggen", @@ -512,11 +512,11 @@ "EditCollectionItemsHelp": "Entferne oder f\u00fcge alle Filme, Serien, Alben, B\u00fccher oder Spiele, die du in dieser Sammlung gruppieren willst hinzu.", "HeaderAddTitles": "Titel hinzuf\u00fcgen", "LabelEnableDlnaPlayTo": "Aktiviere DLNA Play To", - "LabelEnableDlnaPlayToHelp": "Emby can detect devices within your network and offer the ability to remote control them.", + "LabelEnableDlnaPlayToHelp": "Emby kann Ger\u00e4te in Ihrem Netzwerk erkennen und bietet Ihnen die M\u00f6glichkeit diese fernzusteuern.", "LabelEnableDlnaDebugLogging": "Aktiviere DLNA Debug Logging", "LabelEnableDlnaDebugLoggingHelp": "Dies wird gro\u00dfe Logdateien erzeugen und sollte nur zur Fehlerbehebung benutzt werden.", "LabelEnableDlnaClientDiscoveryInterval": "Client-Entdeckungs Intervall (Sekunden)", - "LabelEnableDlnaClientDiscoveryIntervalHelp": "Determines the duration in seconds between SSDP searches performed by Emby.", + "LabelEnableDlnaClientDiscoveryIntervalHelp": "Ermittelt die Zeit in Sekunden zwischen SSDP Suchanfragen die durch Emby ausgef\u00fchrt wurden.", "HeaderCustomDlnaProfiles": "Benutzerdefinierte Profile", "HeaderSystemDlnaProfiles": "Systemprofile", "CustomDlnaProfilesHelp": "Erstelle ein benutzerdefiniertes Profil f\u00fcr ein neues Zielger\u00e4t, oder um ein vorhandenes Systemprofil zu \u00fcberschreiben.", @@ -533,7 +533,7 @@ "LabelFriendlyServerName": "Freundlicher Servername:", "LabelFriendlyServerNameHelp": "Dieser Name wird benutzt um diesen Server zu identifizieren. Wenn leer gelassen, wird der Computername benutzt.", "LabelPreferredDisplayLanguage": "Bevorzugte Anzeigesprache:", - "LabelPreferredDisplayLanguageHelp": "Translating Emby is an ongoing project and is not yet complete.", + "LabelPreferredDisplayLanguageHelp": "Die \u00dcbersetzung von Emby wird stetig fortgesetzt und ist aktuell noch nicht vollst\u00e4ndig.", "LabelReadHowYouCanContribute": "Lese wie du dazu beitragen kannst.", "HeaderNewCollection": "Neue Collection", "ButtonSubmit": "Best\u00e4tigen", @@ -541,7 +541,7 @@ "LabelCustomCss": "Benutzerdefinierte CSS:", "LabelCustomCssHelp": "Wende deine eigene, benutzerdefinierte CSS f\u00fcr das Webinterface an.", "LabelLocalHttpServerPortNumber": "Lokale HTTP Portnummer:", - "LabelLocalHttpServerPortNumberHelp": "The tcp port number that Emby's http server should bind to.", + "LabelLocalHttpServerPortNumberHelp": "Die TCP Port Nummer, auf die der Emby http Server h\u00f6rt.", "LabelPublicHttpPort": "\u00d6ffentliche HTTP Portnummer:", "LabelPublicHttpPortHelp": "Die \u00f6ffentliche Portnummer sollte einem lokalen HTTP Port zugewiesen werden.", "LabelPublicHttpsPort": "\u00d6ffentliche HTTPS Portnummer:", @@ -549,12 +549,12 @@ "LabelEnableHttps": "Gebe HTTPS als externe Adresse aus", "LabelEnableHttpsHelp": "Falls aktiviert, gibt der Server eine HTTPS URL als seine externe Adresse an die Clients weiter. Dies kann Clients deaktivieren, die kein HTTPS unterst\u00fctzen.", "LabelHttpsPort": "Lokale HTTPS Portnummer:", - "LabelHttpsPortHelp": "The tcp port number that Emby's https server should bind to.", + "LabelHttpsPortHelp": "Die TCP Port-Nummer f\u00fcr sichere Emby https Verbindungen.", "LabelWebSocketPortNumber": "Web Socket Port Nummer:", "LabelEnableAutomaticPortMap": "Aktiviere das automatische Port-Mapping", "LabelEnableAutomaticPortMapHelp": "Versuche automatisch den \u00f6ffentlichen Port dem lokalen Port mit Hilfe von UPnP zuzuordnen. Dies kann mit einigen Router-Modellen nicht funktionieren.", "LabelExternalDDNS": "Externe WAN Adresse:", - "LabelExternalDDNSHelp": "If you have a dynamic DNS enter it here. Emby apps will use it when connecting remotely. Leave empty for automatic detection.", + "LabelExternalDDNSHelp": "Wenn Sie einen dynamischen DNS verwenden, geben Sie diesen hier ein. Emby Apps werden diese bei Internetverbindungen automatisch verwenden. Lassen Sie dieses Feld f\u00fcr eine automatische Erkennung leer.", "TabResume": "Fortsetzen", "TabWeather": "Wetter", "TitleAppSettings": "App Einstellungen", @@ -581,7 +581,7 @@ "LabelEpisodeNumber": "Episodennummer:", "LabelEndingEpisodeNumber": "Nummer der letzten Episode:", "LabelEndingEpisodeNumberHelp": "Nur erforderlich f\u00fcr Mehrfachepisoden", - "HeaderSupportTheTeam": "Support the Emby Team", + "HeaderSupportTheTeam": "Unterst\u00fctzen Sie das Emby Team", "LabelSupportAmount": "Betrag (USD)", "HeaderSupportTheTeamHelp": "Hilf bei der Weiterentwicklung dieses Projekts indem du spendest. Ein Teil der Spenden wird an freie Anwendungen auf die wir angewiesen sind weiter gespendet.", "ButtonEnterSupporterKey": "Supporter Key eintragen", @@ -613,7 +613,7 @@ "OptionMove": "Verschieben", "LabelTransferMethodHelp": "Kopiere oder verschiebe Dateien aus dem \u00dcberwachungsverzeichnis", "HeaderLatestNews": "Neueste Nachrichten", - "HeaderHelpImproveProject": "Help Improve Emby", + "HeaderHelpImproveProject": "Helfen Sie, Emby zu verbessern", "HeaderRunningTasks": "Laufende Aufgaben", "HeaderActiveDevices": "Aktive Ger\u00e4te", "HeaderPendingInstallations": "Ausstehende Installationen", @@ -624,8 +624,8 @@ "ButtonUpdateNow": "Jetzt aktualisieren", "TabHosting": "Hosting", "PleaseUpdateManually": "Bitte herunterfahren und den Server manuell aktualisieren.", - "NewServerVersionAvailable": "A new version of Emby Server is available!", - "ServerUpToDate": "Emby Server is up to date", + "NewServerVersionAvailable": "Eine neue Version des Emby Servers ist verf\u00fcgbar!", + "ServerUpToDate": "Emby Server ist auf dem aktuellsten Stand.", "LabelComponentsUpdated": "Die folgenden Komponenten wurden installiert oder aktualisiert:", "MessagePleaseRestartServerToFinishUpdating": "Bitte den Server neustarten, um die Aktualisierungen abzuschlie\u00dfen.", "LabelDownMixAudioScale": "Audio Verst\u00e4rkung bei Downmixing:", @@ -642,13 +642,13 @@ "LabelSupporterEmailAddress": "Die E-Mail Adresse, die zum Kauf des Schl\u00fcssels benutzt wurde.", "ButtonRetrieveKey": "Schl\u00fcssel wiederherstellen", "LabelSupporterKey": "Unterst\u00fctzerschl\u00fcssel (einf\u00fcgen aus E-Mail)", - "LabelSupporterKeyHelp": "Enter your supporter key to start enjoying additional benefits the community has developed for Emby.", + "LabelSupporterKeyHelp": "Geben Sie Ihren Unterst\u00fctzer-Schl\u00fcssel an um weitere Funktionen der Entwickler-Community f\u00fcr Emby freizuschalten.", "MessageInvalidKey": "MB3 Schl\u00fcssel nicht vorhanden oder ung\u00fcltig.", - "ErrorMessageInvalidKey": "In order for any premium content to be registered, you must also be an Emby Supporter. Please donate and support the continued development of the core product. Thank you.", + "ErrorMessageInvalidKey": "Wenn Sie sich f\u00fcr Premium Inhalte registrieren m\u00f6chten, ben\u00f6tigen Sie zuvor einen Emby Unterst\u00fctzer Schl\u00fcssel. Bitte Spenden und unterst\u00fctzen Sie die zuk\u00fcnftige Entwicklung des Hauptprodukts. Vielen Dank.", "HeaderDisplaySettings": "Anzeige Einstellungen", "TabPlayTo": "Spiele an", "LabelEnableDlnaServer": "Aktiviere DLNA Server", - "LabelEnableDlnaServerHelp": "Allows UPnP devices on your network to browse and play Emby content.", + "LabelEnableDlnaServerHelp": "Erlaubt UPnP Ger\u00e4ten in Ihrem Netzwerk Zugriff und Wiedergabe von Emby Inhalten.", "LabelEnableBlastAliveMessages": "Erzeuge Alive Meldungen", "LabelEnableBlastAliveMessagesHelp": "Aktiviere dies, wenn der Server nicht zuverl\u00e4ssig von anderen UPnP Ger\u00e4ten in ihrem Netzwerk erkannt wird.", "LabelBlastMessageInterval": "Alive Meldungsintervall (Sekunden)", @@ -788,7 +788,7 @@ "LabelIconMaxHeight": "Maximale Iconh\u00f6he:", "LabelIconMaxHeightHelp": "Maximale Aufl\u00f6sung f\u00fcr durch UPnP \u00fcbermittelte Icons:icon.", "LabelIdentificationFieldHelp": "Ein Teilstring oder Regex Ausdruck, der keine Gro\u00df- und Kleinschreibung ber\u00fccksichtigt.", - "HeaderProfileServerSettingsHelp": "These values control how Emby Server will present itself to the device.", + "HeaderProfileServerSettingsHelp": "Diese Werte geben an, wie Emby Server sich Ihren Ger\u00e4ten pr\u00e4sentiert.", "LabelMaxBitrate": "Maximale Bitrate:", "LabelMaxBitrateHelp": "Lege eine maximale Bitrate, f\u00fcr Anwendungsgebiete mit begrenzter Bandbreite oder bei durch die Endger\u00e4te auferlegten Banbdbreitenbegrenzungen, fest", "LabelMaxStreamingBitrate": "Maximale Streamingbitrate", @@ -829,7 +829,7 @@ "OptionEstimateContentLength": "Voraussichtliche Inhaltsl\u00e4nge beim Transkodieren", "OptionReportByteRangeSeekingWhenTranscoding": "Teilt die Unterst\u00fctzung der Bytesuche w\u00e4hrend des transkodierens auf dem Server mit.", "OptionReportByteRangeSeekingWhenTranscodingHelp": "Dies wird f\u00fcr manche Abspielger\u00e4te ben\u00f6tigt, auf denen die Zeitsuche nicht gut funktioniert.", - "HeaderSubtitleDownloadingHelp": "When Emby scans your video files it can search for missing subtitles, and download them using a subtitle provider such as OpenSubtitles.org.", + "HeaderSubtitleDownloadingHelp": "Wenn Emby Ihre Videodateien untersucht, kann Emby fehlende Untertitel f\u00fcr Sie suchen und mit einem Untertitel Anbieter, z.B. OpenSubtitles.org, herunterladen.", "HeaderDownloadSubtitlesFor": "Lade Untertitel runter f\u00fcr", "MessageNoChapterProviders": "Installiere ein Plugin f\u00fcr Kapitelinhalte, wie beispielsweise ChapterDb, um weitere Optionen f\u00fcr Kapitel zu erhalten.", "LabelSkipIfGraphicalSubsPresent": "\u00dcberspringen, falls das Video bereits grafische Untertitel enth\u00e4lt", @@ -839,7 +839,7 @@ "HeaderDownloadChaptersFor": "Lade Kapitelnamen herunter f\u00fcr:", "LabelOpenSubtitlesUsername": "\"Open Subtitles\" Benutzername:", "LabelOpenSubtitlesPassword": "\"Open Subtitles\" Passwort:", - "HeaderChapterDownloadingHelp": "When Emby scans your video files it can download friendly chapter names from the internet using chapter plugins such as ChapterDb.", + "HeaderChapterDownloadingHelp": "Wenn Emby Ihre Videodateien untersucht, so kann Emby Kapitelnamen f\u00fcr Sie mit Hilfe eines Kapitel-Plugins, z.B. ChapterDb, herunterladen.", "LabelPlayDefaultAudioTrack": "Spiele unabh\u00e4ngig von der Sprache die Standardtonspur", "LabelSubtitlePlaybackMode": "Untertitel Modus:", "LabelDownloadLanguages": "Herunterzuladende Sprachen:", @@ -902,7 +902,7 @@ "OptionDefaultSort": "Default", "OptionCommunityMostWatchedSort": "Meistgesehen", "TabNextUp": "Als N\u00e4chstes", - "HeaderBecomeProjectSupporter": "Become an Emby Supporter", + "HeaderBecomeProjectSupporter": "Werden Sie Emby Unterst\u00fctzer", "TextEnjoyBonusFeatures": "Erleben Sie Bonus Funktionen", "MessageNoMovieSuggestionsAvailable": "Momentan sind keine Filmvorschl\u00e4ge verf\u00fcgbar. Schaue und bewerte zuerst deine Filme. Komme danach zur\u00fcck, um deine Filmvorschl\u00e4ge anzuschauen.", "MessageNoCollectionsAvailable": "Sammlungen erlauben Ihnen eine personalisierte Gruppierung von Filmen, Serien, Alben, B\u00fcchern und Spielen. Klicken Sie die + Schaltfl\u00e4che um Sammlungen zu erstellen.", @@ -969,9 +969,9 @@ "LabelProtocolInfo": "Protokoll Information:", "LabelProtocolInfoHelp": "Der Wert, der f\u00fcr die Beantwortung von GetProtocolInfo Anfragen durch die Endger\u00e4te benutzt wird.", "TabNfo": "Nfo", - "HeaderKodiMetadataHelp": "Emby includes native support for Nfo metadata files. To enable or disable Nfo metadata, use the Advanced tab to configure options for your media types.", + "HeaderKodiMetadataHelp": "Emby beinhaltet eine native Unterst\u00fctzung von Nfo Metadata Dateien. Um Nfo Metadata ein oder auszuschalten, verwenden Sie den Erweitert Tab.", "LabelKodiMetadataUser": "Synchronisiere den \"Gesehen\" Status von Benutzern in NFO's f\u00fcr:", - "LabelKodiMetadataUserHelp": "Enable this to keep watch data in sync between Emby Server and Nfo files.", + "LabelKodiMetadataUserHelp": "Aktivieren Sie dies, um den \"Gesehen\" Status zwischen Emby Server und Nfo Dateien synchron zu halten.", "LabelKodiMetadataDateFormat": "Ver\u00f6ffentlichungsdatum Format:", "LabelKodiMetadataDateFormatHelp": "Alle Daten in den NFO's werde unter Benutzung dieses Format gelesen und geschrieben.", "LabelKodiMetadataSaveImagePaths": "Speicher Bildpfade innerhalb der NFO Dateien", @@ -989,7 +989,7 @@ "TabLogs": "Logs", "HeaderServerLogFiles": "Server Logdateien", "TabBranding": "Markierung", - "HeaderBrandingHelp": "Customize the appearance of Emby to fit the needs of your group or organization.", + "HeaderBrandingHelp": "Personalisieren Sie das Erscheinen von Empy um es Ihren eigenen Bed\u00fcrfnissen, oder die Ihrer Organisation, anzupassen.", "LabelLoginDisclaimer": "Anmeldung Haftungsausschluss:", "LabelLoginDisclaimerHelp": "Dies wird am Boden des Anmeldebildschirms angezeigt.", "LabelAutomaticallyDonate": "Spende diesen Geldbetrag jeden Monat automatisch", @@ -1005,7 +1005,7 @@ "HeaderLatestMusic": "Neueste Musik", "HeaderBranding": "Markierung", "HeaderApiKeys": "API Schl\u00fcssel", - "HeaderApiKeysHelp": "External applications are required to have an Api key in order to communicate with Emby Server. Keys are issued by logging in with an Emby account, or by manually granting the application a key.", + "HeaderApiKeysHelp": "Externe Applikationen ben\u00f6tigen einen API Key um mit Emby Server zu kommunizieren. API Keys werden beim loggin mit einem Emby Konto vergeben oder durch eine manuelle Freigabe.", "HeaderApiKey": "API Schl\u00fcssel", "HeaderApp": "App", "HeaderDevice": "Endger\u00e4t", @@ -1015,7 +1015,7 @@ "HeaderNewApiKey": "Neuer API Schl\u00fcssel", "LabelAppName": "App Name", "LabelAppNameExample": "Beispiel: Sickbeard, NzbDrone", - "HeaderNewApiKeyHelp": "Grant an application permission to communicate with Emby Server.", + "HeaderNewApiKeyHelp": "Geben Sie einer Applikation die Erlaubnis mit dem Emby Server zu kommunizieren.", "HeaderHttpHeaders": "Http Headers", "HeaderIdentificationHeader": "Identfikations Header", "LabelValue": "Wert:", @@ -1119,7 +1119,7 @@ "UserDeletedWithName": "Benutzer {0} wurde gel\u00f6scht", "MessageServerConfigurationUpdated": "Server Einstellungen wurden aktualisiert", "MessageNamedServerConfigurationUpdatedWithValue": "Der Server Einstellungsbereich {0} wurde aktualisiert", - "MessageApplicationUpdated": "Emby Server has been updated", + "MessageApplicationUpdated": "Emby Server wurde auf den neusten Stand gebracht.", "AuthenticationSucceededWithUserName": "{0} erfolgreich authentifiziert", "FailedLoginAttemptWithUserName": "Fehlgeschlagener Anmeldeversuch von {0}", "UserDownloadingItemWithValues": "{0} l\u00e4dt {1} herunter", @@ -1140,14 +1140,14 @@ "ViewTypeLiveTvRecordingGroups": "Aufnahmen", "ViewTypeLiveTvChannels": "Kan\u00e4le", "LabelEasyPinCode": "Einfacher pin code:", - "EasyPasswordHelp": "Your easy pin code is used for offline access with supported Emby apps, and can also be used for easy in-network sign in.", + "EasyPasswordHelp": "Ihre vereinfachte PIN Eingabe wird f\u00fcr offline Zugriffe von unterst\u00fctzenden Emby Apps verwendet. Sie kann ebenso als erleichterten Zugang aus dem eigenen Netzwerk verwendet werden.", "LabelInNetworkSignInWithEasyPassword": "Schalte Login mit einfachen Passwort f\u00fcr das eigene Netzwerk ein.", - "LabelInNetworkSignInWithEasyPasswordHelp": "If enabled, you'll be able to use your easy pin code to sign in to Emby apps from inside your home network. Your regular password will only be needed away from home. If the pin code is left blank, you won't need a password within your home network.", + "LabelInNetworkSignInWithEasyPasswordHelp": "Wenn aktiviert, k\u00f6nnen Sie sich in ihrem eigenen Netzwerk mit dem vereinfachten PIN bei Emby Apps anmelden. Ihr regul\u00e4res Kennwort wird nur ben\u00f6tigt, wenn Sie unterwegs sind. Wenn Sie den PIN frei lassen, so ben\u00f6tigen Sie in Ihrem Netzwerk keinen PIN.", "HeaderPassword": "Passwort", "HeaderLocalAccess": "Lokaler Zugriff", "HeaderViewOrder": "Reihenfolge f\u00fcr Ansichten", "ButtonResetEasyPassword": "Einfachen PIN zur\u00fccksetzen", - "LabelSelectUserViewOrder": "Choose the order your views will be displayed in within Emby apps", + "LabelSelectUserViewOrder": "W\u00e4hlen Sie die Reihenfolge der Ansichten in Ihren Emby apps aus.", "LabelMetadataRefreshMode": "Metadaten Aktualisierungsmethode:", "LabelImageRefreshMode": "Aktualisierungsmethode f\u00fcr Bilder:", "OptionDownloadMissingImages": "Lade fehlende Bilder herunter", @@ -1249,10 +1249,10 @@ "OptionSaveMetadataAsHidden": "Speichere Metadaten und Bilder als versteckte Dateien", "LabelExtractChaptersDuringLibraryScan": "Erzeuge Kapitelbilder w\u00e4hrend des scannens der Bibliothek", "LabelExtractChaptersDuringLibraryScanHelp": "Fall aktiviert, werden Kapitelbilder w\u00e4hrend des Imports von Videos beim Bibliothekenscan erzeugt. Falls deaktiviert, werden die Kapitelbilder w\u00e4hrend einer eigens daf\u00fcr geplanten Aufgabe erstellt, was den Bibliothekenscan beschleunigt.", - "LabelConnectGuestUserName": "Their Emby username or email address:", - "LabelConnectUserName": "Emby username\/email:", - "LabelConnectUserNameHelp": "Connect this user to an Emby account to enable easy sign-in access from any Emby app without having to know the server ip address.", - "ButtonLearnMoreAboutEmbyConnect": "Learn more about Emby Connect", + "LabelConnectGuestUserName": "Ihr Emby Benutzername oder Emailadresse:", + "LabelConnectUserName": "Emby Benutzername\/ Email-Adresse:", + "LabelConnectUserNameHelp": "Verbinden Sie diesen Benutzer mit einem Emby Konto um eine leichte erleichterte Verbindung ohne der Kenntnis einer Server IP Adresse zu erm\u00f6glichen.", + "ButtonLearnMoreAboutEmbyConnect": "Erfahren Sie mehr \u00fcber Emby-Connect", "LabelExternalPlayers": "Externe Abspielger\u00e4te:", "LabelExternalPlayersHelp": "Zeige Buttons um Inhalt auf externen Ger\u00e4te abzuspielen. Dies ist nur auf Ger\u00e4ten verf\u00fcgbar, die URL Schemes unterst\u00fctzen (Generell Android und iOS). In Verbindung mit externen Abspielern gibt es generell keine Unterst\u00fctzung f\u00fcr die Fernbedienung oder die Fortsetzung von gesehenen Inhalten.", "HeaderSubtitleProfile": "Untertitel Profil", @@ -1300,7 +1300,7 @@ "TitleDevices": "Ger\u00e4te", "TabCameraUpload": "Kamera-Upload", "TabDevices": "Ger\u00e4te", - "HeaderCameraUploadHelp": "Automatically upload photos and videos taken from your mobile devices into Emby.", + "HeaderCameraUploadHelp": "Lade automatisch Fotos und Videos, die von Ihrem Mobilger\u00e4t gemacht wurden, nach Emby hoch.", "MessageNoDevicesSupportCameraUpload": "Du hast bis jetzt keine Ger\u00e4t die den Kamera-Upload unterst\u00fctzen.", "LabelCameraUploadPath": "Kamera-Upload Pfad:", "LabelCameraUploadPathHelp": "W\u00e4hle, falls gew\u00fcnscht, einen eigenen Upload Pfad. Wird keiner festgelegt, so wird der Standard-Pfad verwendet. Ein eigener Pfad muss zus\u00e4tzlich in der Medien Bibliothek hinzugef\u00fcgt werden!", @@ -1309,10 +1309,10 @@ "LabelCustomDeviceDisplayName": "Angezeigter Name:", "LabelCustomDeviceDisplayNameHelp": "Lege einen individuellen Anzeigenamen fest oder lasse das Feld leer, um den vom ger\u00e4t \u00fcbermittelten Namen zu nutzen.", "HeaderInviteUser": "Lade Benutzer ein", - "LabelConnectGuestUserNameHelp": "This is the username that your friend uses to sign in to the Emby website, or their email address.", - "HeaderInviteUserHelp": "Sharing your media with friends is easier than ever before with Emby Connect.", + "LabelConnectGuestUserNameHelp": "Dies ist der Benutzername oder die Emailadresse die Ihr Freund verwendet um sich auf der Emby Website anzumelden.", + "HeaderInviteUserHelp": "Das Tauschen von Medien mit Freunden ist mit Emby Connect leichter als jemals zuvor.", "ButtonSendInvitation": "Sende Einladung", - "HeaderSignInWithConnect": "Sign in with Emby Connect", + "HeaderSignInWithConnect": "Anmelden mit Emby Connect", "HeaderGuests": "G\u00e4ste", "HeaderLocalUsers": "Lokale Benutzer", "HeaderPendingInvitations": "Ausstehende Einladungen", @@ -1327,8 +1327,8 @@ "OptionEveryday": "T\u00e4glich", "OptionWeekdays": "W\u00f6chentlich", "OptionWeekends": "An Wochenenden", - "MessageProfileInfoSynced": "User profile information synced with Emby Connect.", - "HeaderOptionalLinkEmbyAccount": "Optional: Link your Emby account", + "MessageProfileInfoSynced": "Benutzerprofil Informationen mit Emby Connect synchronisiert", + "HeaderOptionalLinkEmbyAccount": "Optional: Verbinden Sie Ihr Emby Konto", "ButtonTrailerReel": "Trailer Rolle", "HeaderTrailerReel": "Trailer Rolle", "OptionPlayUnwatchedTrailersOnly": "Spiele nur bisher nicht gesehene Trailer", @@ -1384,5 +1384,7 @@ "HeaderUpcomingPrograms": "Bevorstehende Programme", "ButtonMoreItems": "Mehr...", "LabelShowLibraryTileNames": "Zeige Bibliothek Kachelnamen.", - "LabelShowLibraryTileNamesHelp": "Legen Sie fest, ob Beschriftungen unter den Kacheln der Startseite angezeigt werden sollen." + "LabelShowLibraryTileNamesHelp": "Legen Sie fest, ob Beschriftungen unter den Kacheln der Startseite angezeigt werden sollen.", + "OptionEnableTranscodingThrottle": "Enable throttling", + "OptionEnableTranscodingThrottleHelp": "Throttling will automatically adjust transcoding speed in order to minimize server cpu utilization during playback." } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/el.json b/MediaBrowser.Server.Implementations/Localization/Server/el.json index 8afb74550e..96b67ae6e8 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/el.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/el.json @@ -1384,5 +1384,7 @@ "HeaderUpcomingPrograms": "Upcoming Programs", "ButtonMoreItems": "More...", "LabelShowLibraryTileNames": "Show library tile names", - "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page" + "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page", + "OptionEnableTranscodingThrottle": "Enable throttling", + "OptionEnableTranscodingThrottleHelp": "Throttling will automatically adjust transcoding speed in order to minimize server cpu utilization during playback." } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/en_GB.json b/MediaBrowser.Server.Implementations/Localization/Server/en_GB.json index 49858907f7..81bee1153f 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/en_GB.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/en_GB.json @@ -1384,5 +1384,7 @@ "HeaderUpcomingPrograms": "Upcoming Programs", "ButtonMoreItems": "More...", "LabelShowLibraryTileNames": "Show library tile names", - "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page" + "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page", + "OptionEnableTranscodingThrottle": "Enable throttling", + "OptionEnableTranscodingThrottleHelp": "Throttling will automatically adjust transcoding speed in order to minimize server cpu utilization during playback." } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/en_US.json b/MediaBrowser.Server.Implementations/Localization/Server/en_US.json index a5a48ff67a..86cb406b53 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/en_US.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/en_US.json @@ -1384,5 +1384,7 @@ "HeaderUpcomingPrograms": "Upcoming Programs", "ButtonMoreItems": "More...", "LabelShowLibraryTileNames": "Show library tile names", - "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page" + "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page", + "OptionEnableTranscodingThrottle": "Enable throttling", + "OptionEnableTranscodingThrottleHelp": "Throttling will automatically adjust transcoding speed in order to minimize server cpu utilization during playback." } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/es.json b/MediaBrowser.Server.Implementations/Localization/Server/es.json index 48dd02e50d..c1c3605b79 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/es.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/es.json @@ -1384,5 +1384,7 @@ "HeaderUpcomingPrograms": "Upcoming Programs", "ButtonMoreItems": "More...", "LabelShowLibraryTileNames": "Show library tile names", - "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page" + "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page", + "OptionEnableTranscodingThrottle": "Enable throttling", + "OptionEnableTranscodingThrottleHelp": "Throttling will automatically adjust transcoding speed in order to minimize server cpu utilization during playback." } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/es_MX.json b/MediaBrowser.Server.Implementations/Localization/Server/es_MX.json index 4f1e0e4f0d..26a7e7f17e 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/es_MX.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/es_MX.json @@ -26,7 +26,7 @@ "AWindowsServiceHasBeenInstalled": "Se ha instalado un Servicio de Windows.", "WindowsServiceIntro1": "El Servidor Emby normalmente se ejecuta como una aplicaci\u00f3n de escritorio con un icono de bandeja, pero si prefiere ejecutarlo como un servicio de fondo, puede en su lugar ser iniciado del panel de control de servicios de windows.", "WindowsServiceIntro2": "Si utiliza el servicio de Windows, tenga en cuenta que no se puede ejecutar simult\u00e1neamiente con el icono en el \u00e1rea de notificaci\u00f3n, por lo que tendr\u00e1 que finalizar desde el icono para poder ejecutar el servicio. Adicionalmente, el servicio deber\u00e1 ser configurado con privilegios administrativos a trav\u00e9s del panel de control del servicio. Tenga en cuenta que en este momento el servicio no es capaz de actualizarse a s\u00ed mismo, por lo que las nuevas versiones requerir\u00e1n de interacci\u00f3n manual.", - "WizardCompleted": "Eso es todo lo que necesitamos por ahora, Emby ha comenzado a recolectar informaci\u00f3n sobre su biblioteca de medios. Revise algunas de nuestras aplicaciones, y de clic en Finalizar<\/b> para ver el Panel de Control<\/b>", + "WizardCompleted": "Eso es todo lo que necesitamos por ahora, Emby ha comenzado a recolectar informaci\u00f3n sobre su biblioteca de medios. Revise algunas de nuestras aplicaciones, y haga clic en Finalizar<\/b> para ver el Panel de Control<\/b>", "LabelConfigureSettings": "Configuraci\u00f3n de opciones", "LabelEnableVideoImageExtraction": "Habilitar extracci\u00f3n de im\u00e1genes de video", "VideoImageExtractionHelp": "Para videos que no cuenten con im\u00e1genes, y para los que no podemos encontrar im\u00e1genes en Internet. Esto incrementar\u00e1 un poco el tiempo de la exploraci\u00f3n inicial de las bibliotecas, pero resultar\u00e1 en una presentaci\u00f3n m\u00e1s agradable.", @@ -191,7 +191,7 @@ "HeaderLatestEpisodes": "Episodios Recientes", "HeaderPersonTypes": "Tipos de Personas:", "TabSongs": "Canciones", - "TabAlbums": "\u00c1lbums", + "TabAlbums": "\u00c1lbumes", "TabArtists": "Artistas", "TabAlbumArtists": "Artistas del \u00c1lbum", "TabMusicVideos": "Videos Musicales", @@ -233,7 +233,7 @@ "TitlePlugins": "Complementos", "HeaderAutomaticUpdates": "Actualizaciones Autom\u00e1ticas", "HeaderNowPlaying": "Reproduciendo Ahora", - "HeaderLatestAlbums": "\u00c1lbums Recientes", + "HeaderLatestAlbums": "\u00c1lbumes Recientes", "HeaderLatestSongs": "Canciones Recientes", "HeaderRecentlyPlayed": "Reproducido Recientemente", "HeaderFrequentlyPlayed": "Reproducido Frecuentemente", @@ -289,13 +289,13 @@ "TabSupporterKey": "Clave de Aficionado", "TabBecomeSupporter": "Convertirse en Aficionado", "ProjectHasCommunity": "Emby cuenta con una pr\u00f3spera comunidad de usuarios y colaboradores.", - "CheckoutKnowledgeBase": "Eche un vistazo a nuestra base de conocimiento para ayudarte a sacar el m\u00e1ximo provecho a Emby", + "CheckoutKnowledgeBase": "Eche un vistazo a nuestra base de conocimiento para ayudarle a sacar el m\u00e1ximo provecho a Emby", "SearchKnowledgeBase": "Buscar en la Base de Conocimiento", "VisitTheCommunity": "Visitar la Comunidad", "VisitProjectWebsite": "Visitar el Sitio Web de Emby", "VisitProjectWebsiteLong": "Visite el sitio Web para conocer las ultimas noticias y mantenerse al d\u00eda con el blog de desarrolladores.", "OptionHideUser": "Ocultar este usuario en las pantallas de inicio de sesi\u00f3n", - "OptionHideUserFromLoginHelp": "\u00datil para cuentas privadas o de administrador ocultas. El usuario tendr\u00e1 que iniciar sesi\u00f3n manualmente ingresando su nombre de usuario y contrase\u00f1a.", + "OptionHideUserFromLoginHelp": "\u00datil para cuentas privadas o de administrador ocultas. El usuario tendr\u00e1 que iniciar sesi\u00f3n manualmente introduciendo su nombre de usuario y contrase\u00f1a.", "OptionDisableUser": "Desactivar este usuario", "OptionDisableUserHelp": "Si est\u00e1 desactivado, el servidor no aceptar\u00e1 conexiones de este usuario. Las conexiones existentes ser\u00e1n finalizadas abruptamente.", "HeaderAdvancedControl": "Control Avanzado", @@ -368,7 +368,7 @@ "LabelMetadataDownloadLanguage": "Lenguaje preferido para descargas:", "ButtonAutoScroll": "Auto-desplazamiento", "LabelImageSavingConvention": "Convenci\u00f3n de almacenamiento de im\u00e1genes:", - "LabelImageSavingConventionHelp": "Emby reconoce im\u00e1genes de la muchas de las mayores aplicaciones de medios. Eligiendo la convenci\u00f3n de descargas es \u00fatil si tambi\u00e9n usa otros productos", + "LabelImageSavingConventionHelp": "Emby reconoce im\u00e1genes de la mayor\u00eda de las principales aplicaciones de medios. Seleccionar sus convenci\u00f3n de descarga es \u00fatil si tambi\u00e9n usa otros productos.", "OptionImageSavingCompatible": "Compatible - Emby\/Kodi\/Plex", "OptionImageSavingStandard": "Est\u00e1ndar - MB2", "ButtonSignIn": "Iniciar Sesi\u00f3n", @@ -394,7 +394,7 @@ "LabelPostPaddingMinutes": "Minutos de protecci\u00f3n posterior:", "OptionPostPaddingRequired": "Protecci\u00f3n posterior es requerida para grabar.", "HeaderWhatsOnTV": "\u00bfQu\u00e9 hay?", - "HeaderUpcomingTV": "Pr\u00f3ximos Programas", + "HeaderUpcomingTV": "Programas por Estrenar", "TabStatus": "Estado", "TabSettings": "Configuraci\u00f3n", "ButtonRefreshGuideData": "Actualizar Datos de la Gu\u00eda", @@ -516,7 +516,7 @@ "LabelEnableDlnaDebugLogging": "Habilitar el registro de DLNA en la bit\u00e1cora", "LabelEnableDlnaDebugLoggingHelp": "Esto crear\u00e1 archivos de bit\u00e1cora muy grandes y solo se recomienda cuando se requiera solucionar problemas.", "LabelEnableDlnaClientDiscoveryInterval": "Intervalo de Detecci\u00f3n de Clientes (segundos)", - "LabelEnableDlnaClientDiscoveryIntervalHelp": "Determina la duraci\u00f3n en segundos entre b\u00fasquedas SSDP realizadas por Emby", + "LabelEnableDlnaClientDiscoveryIntervalHelp": "Determina la duraci\u00f3n en segundos entre b\u00fasquedas SSDP realizadas por Emby.", "HeaderCustomDlnaProfiles": "Perfiles Personalizados", "HeaderSystemDlnaProfiles": "Perfiles del Sistema", "CustomDlnaProfilesHelp": "Crear un perfil personalizado para un nuevo dispositivo o reemplazar un perfil del sistema.", @@ -541,7 +541,7 @@ "LabelCustomCss": "css personalizado:", "LabelCustomCssHelp": "Aplicar tu propia css personalizada a la interfaz web.", "LabelLocalHttpServerPortNumber": "N\u00famero de puerto http local:", - "LabelLocalHttpServerPortNumberHelp": "El numero de puerto tcp al que el servidor http Emby deber\u00e1 vincularse.", + "LabelLocalHttpServerPortNumberHelp": "El numero de puerto tcp con el que se deber\u00e1 vincular el servidor http de Emby.", "LabelPublicHttpPort": "N\u00famero de puerto http publico:", "LabelPublicHttpPortHelp": "El numero de puerto que debe ser mapeado a el puerto http local.", "LabelPublicHttpsPort": "N\u00famero de puerto https publico:", @@ -549,7 +549,7 @@ "LabelEnableHttps": "Reportar https como una direcci\u00f3n externa", "LabelEnableHttpsHelp": "Si se habilita, el servidor reportara a los clientes una url https como su direcci\u00f3n externa. Esto podr\u00eda inutilizar a los clientes que aun no soporten https.", "LabelHttpsPort": "N\u00famero de puerto https local:", - "LabelHttpsPortHelp": "El n\u00famero de puerto tcp al que el servidor https Emby deber\u00e1 vincularse.", + "LabelHttpsPortHelp": "El numero de puerto tcp con el que se deber\u00e1 vincular el servidor https de Emby.", "LabelWebSocketPortNumber": "N\u00famero de puerto WebSocket:", "LabelEnableAutomaticPortMap": "Habilitar mapeo autom\u00e1tico de puertos", "LabelEnableAutomaticPortMapHelp": "Intentar mapear autom\u00e1ticamente el puerto p\u00fablico con el puerto local via UPnP. Esto podr\u00eda no funcionar con algunos modelos de ruteadores.", @@ -584,8 +584,8 @@ "HeaderSupportTheTeam": "Apoye al equipo de Emby", "LabelSupportAmount": "Importe (USD)", "HeaderSupportTheTeamHelp": "Ayude a garantizar el desarrollo continuo de este proyecto mediante una donaci\u00f3n. Una parte de todas las donaciones se destinar\u00e1 a otras herramientas gratuitas de las que dependemos.", - "ButtonEnterSupporterKey": "Capture la Clave de Aficionado", - "DonationNextStep": "Cuando haya terminado, regrese y capture la clave de seguidor que recibir\u00e1 por correo electr\u00f3nico.", + "ButtonEnterSupporterKey": "Introduzca la Clave de Aficionado", + "DonationNextStep": "Cuando haya terminado, regrese e introduzca la clave de seguidor que recibir\u00e1 por correo electr\u00f3nico.", "AutoOrganizeHelp": "La organizaci\u00f3n autom\u00e1tica monitorea sus carpetas de descarga en busca de nuevos archivos y los mueve a sus carpetas de medios.", "AutoOrganizeTvHelp": "La organizaci\u00f3n de archivos de TV s\u00f3lo agregar\u00e1 episodios a series existentes. No crear\u00e1 carpetas para series nuevas.", "OptionEnableEpisodeOrganization": "Habilitar la organizaci\u00f3n de nuevos episodios", @@ -810,7 +810,7 @@ "LabelModelUrl": "URL del modelo:", "LabelSerialNumber": "N\u00famero de serie:", "LabelDeviceDescription": "Descripci\u00f3n del dispositivo", - "HeaderIdentificationCriteriaHelp": "Capture, al menos, un criterio de identificaci\u00f3n.", + "HeaderIdentificationCriteriaHelp": "Introduzca, al menos, un criterio de identificaci\u00f3n.", "HeaderDirectPlayProfileHelp": "Agregue perfiles de reproducci\u00f3n directa para indicar que formatos puede manejar el dispositivo de manera nativa.", "HeaderTranscodingProfileHelp": "Agruegue perfiles de transcodificaci\u00f3n para indicar que formatos deben ser usados cuando se requiera transcodificar.", "HeaderResponseProfileHelp": "Los perfiles de respuesta proporcionan un medio para personalizar la informaci\u00f3n enviada a un dispositivo cuando se reproducen ciertos tipos de medios.", @@ -839,7 +839,7 @@ "HeaderDownloadChaptersFor": "Descargar nombres de cap\u00edtulos para:", "LabelOpenSubtitlesUsername": "Nombre de usuario de Open Subtitles:", "LabelOpenSubtitlesPassword": "Contrase\u00f1a de Open Subtitles:", - "HeaderChapterDownloadingHelp": "Cuando Emby examina tus archivos de video puede descargar nombres mas amigables desde internet usando complementos de cap\u00edtulos como ChapterDb.", + "HeaderChapterDownloadingHelp": "Cuando Emby examina sus archivos de video puede descargar nombres de cap\u00edtulo mas amigables desde internet usando complementos de cap\u00edtulos como ChapterDb.", "LabelPlayDefaultAudioTrack": "Reproducir la pista de audio por defecto independientemente del lenguaje", "LabelSubtitlePlaybackMode": "Modo de subt\u00edtulo:", "LabelDownloadLanguages": "Descargar lenguajes:", @@ -862,7 +862,7 @@ "LabelSeasonNumberPlain": "N\u00famero de temporada", "LabelEpisodeNumberPlain": "N\u00famero de episodio", "LabelEndingEpisodeNumberPlain": "N\u00famero del episodio final", - "HeaderTypeText": "Capturar Texto", + "HeaderTypeText": "Introduzca Texto", "LabelTypeText": "Texto", "HeaderSearchForSubtitles": "Buscar Subtitulos", "MessageNoSubtitleSearchResultsFound": "No se encontraron resultados en la b\u00fasqueda.", @@ -905,7 +905,7 @@ "HeaderBecomeProjectSupporter": "Convi\u00e9rtete en un Fan\u00e1tico Emby", "TextEnjoyBonusFeatures": "Disfruta de Caracter\u00edsticas Premium", "MessageNoMovieSuggestionsAvailable": "No hay sugerencias de pel\u00edculas disponibles en este momento. Comienza a ver y a calificar tus pel\u00edculas, y regresa para ver tus recomendaciones.", - "MessageNoCollectionsAvailable": "Las colecciones le permiten disfrutar de agrupaciones personalizadas de Pel\u00edculas, Series, \u00c1lbums, Libros y Juegos. Haga clic en el bot\u00f3n + para iniciar la creaci\u00f3n de Colecciones.", + "MessageNoCollectionsAvailable": "Las colecciones le permiten disfrutar de agrupaciones personalizadas de Pel\u00edculas, Series, \u00c1lbumes, Libros y Juegos. Haga clic en el bot\u00f3n + para iniciar la creaci\u00f3n de Colecciones.", "MessageNoPlaylistsAvailable": "Las listas de reproducci\u00f3n le permiten crear listas de contenidos a ser reproducidos de manera consecutiva. Para agregar \u00edtems a una lista de reproducci\u00f3n, haga clic derecho o seleccione y mantenga, despu\u00e9s seleccione Agregar a Lista de Reproducci\u00f3n.", "MessageNoPlaylistItemsAvailable": "Esta lista de reproducci\u00f3n se encuentra vac\u00eda.", "ButtonDismiss": "Descartar", @@ -951,12 +951,12 @@ "ViewTypeMovieFavorites": "Favoritos", "ViewTypeMovieGenres": "G\u00e9neros", "ViewTypeMusicLatest": "Recientes", - "ViewTypeMusicAlbums": "\u00c1lbums", + "ViewTypeMusicAlbums": "\u00c1lbumes", "ViewTypeMusicAlbumArtists": "Artistas del \u00c1lbum", "HeaderOtherDisplaySettings": "Configuraci\u00f3n de Pantalla", "ViewTypeMusicSongs": "Canciones", "ViewTypeMusicFavorites": "Favoritos", - "ViewTypeMusicFavoriteAlbums": "\u00c1lbums Favoritos", + "ViewTypeMusicFavoriteAlbums": "\u00c1lbumes Favoritos", "ViewTypeMusicFavoriteArtists": "Artistas Favoritos", "ViewTypeMusicFavoriteSongs": "Canciones Favoritas", "HeaderMyViews": "Mis Vistas", @@ -971,7 +971,7 @@ "TabNfo": "Nfo", "HeaderKodiMetadataHelp": "Emby incluye soporte nativo para archivos de metadatos Nfo. Para habilitar o deshabilitar los metadatos Nfo, utilice la pesta\u00f1a Avanzado para configurar las opciones para sus tipos de medios.", "LabelKodiMetadataUser": "Sincronizar informaci\u00f3n de vistos a nfo's para:", - "LabelKodiMetadataUserHelp": "Habilitar esto para mantener el monitoreo de datos en sincron\u00eda entre el Servidor Emby y Xbmc.", + "LabelKodiMetadataUserHelp": "Habilitar esto para mantener los datos monitoreados en sincron\u00eda entre el Servidor Emby y los archivos Nfo .", "LabelKodiMetadataDateFormat": "Formato de fecha de estreno:", "LabelKodiMetadataDateFormatHelp": "Todas las fechas en los nfo\u00b4s ser\u00e1n le\u00eddas y escritas utilizando este formato.", "LabelKodiMetadataSaveImagePaths": "Guardar trayectorias de im\u00e1genes en los archivos nfo", @@ -989,7 +989,7 @@ "TabLogs": "Bit\u00e1coras", "HeaderServerLogFiles": "Archivos de registro del servidor:", "TabBranding": "Establecer Marca", - "HeaderBrandingHelp": "Personaliza la apariencia de Emby para ajustarla a tu grupo u organizaci\u00f3n.", + "HeaderBrandingHelp": "Personaliza la apariencia de Emby para ajustarla a su grupo u organizaci\u00f3n.", "LabelLoginDisclaimer": "Aviso de Inicio de Sesi\u00f3n:", "LabelLoginDisclaimerHelp": "Esto se mostrara al final de la pagina de inicio de sesi\u00f3n.", "LabelAutomaticallyDonate": "Donar autom\u00e1ticamente este monto cada mes", @@ -1090,7 +1090,7 @@ "OptionReportCollections": "Colecciones", "OptionReportBooks": "Libros", "OptionReportArtists": "Artistas", - "OptionReportAlbums": "\u00c1lbums", + "OptionReportAlbums": "\u00c1lbumes", "OptionReportAdultVideos": "Videos para Adultos", "ButtonMore": "M\u00e1s", "HeaderActivity": "Actividad", @@ -1140,9 +1140,9 @@ "ViewTypeLiveTvRecordingGroups": "Grabaciones", "ViewTypeLiveTvChannels": "Canales", "LabelEasyPinCode": "C\u00f3digo pin sencillo:", - "EasyPasswordHelp": "Tu c\u00f3digo pin sencillo es usado para el acceso fuera de linea con las aplicaciones Emby soportadas, y tambi\u00e9n puede ser usado para un inicio de sesi\u00f3n mas f\u00e1cil cuando esta conectado a su red.", + "EasyPasswordHelp": "Tu c\u00f3digo pin sencillo es usado para el acceso fuera de linea con las aplicaciones Emby soportadas, y tambi\u00e9n puede ser usado para iniciar sesi\u00f3n m\u00e1s f\u00e1cilmente cuando esta conectado dentro de su red.", "LabelInNetworkSignInWithEasyPassword": "Habilitar inicio de sesi\u00f3n con mi c\u00f3digo pin sencillo para conexiones dentro de la red.", - "LabelInNetworkSignInWithEasyPasswordHelp": "Si se habilita, podr\u00e1 usar su c\u00f3digo pin sencillo para accesar a las aplicaciones Emby desde su red de hogar. Su contrase\u00f1a regular sera requerida solamente cuando este fuera de casa. Si el c\u00f3digo pin es dejado en blanco, no necesitara una contrase\u00f1a dentro de su red de hogar.", + "LabelInNetworkSignInWithEasyPasswordHelp": "Si se habilita, podr\u00e1 usar su c\u00f3digo pin sencillo para iniciar sesi\u00f3n en las aplicaciones Emby desde su red de hogar. Su contrase\u00f1a regular sera requerida solamente cuando este fuera de casa. Si el c\u00f3digo pin es dejado en blanco, no necesitara una contrase\u00f1a dentro de su red de hogar.", "HeaderPassword": "Contrase\u00f1a", "HeaderLocalAccess": "Acceso Local", "HeaderViewOrder": "Orden de Despliegue", @@ -1158,10 +1158,10 @@ "HeaderRefreshMetadata": "Actualizar Metadatos", "HeaderPersonInfo": "Info de la Persona", "HeaderIdentifyItem": "Identificar \u00cdtem", - "HeaderIdentifyItemHelp": "Ingrese uno o m\u00e1s criterios de b\u00fasqueda. Elimine criterios para expandir los resultados.", + "HeaderIdentifyItemHelp": "Introduzca uno o m\u00e1s criterios de b\u00fasqueda. Elimine criterios para expandir los resultados.", "HeaderConfirmDeletion": "Confirmar Eliminaci\u00f3n", "LabelFollowingFileWillBeDeleted": "Lo siguiente ser\u00e1 eliminado:", - "LabelIfYouWishToContinueWithDeletion": "Si desea continuar, por favor confirme ingresando el valor de:", + "LabelIfYouWishToContinueWithDeletion": "Si desea continuar, por favor confirme introduci\u00e9ndo el valor de:", "ButtonIdentify": "Identificar", "LabelAlbumArtist": "Artista del \u00e1lbum:", "LabelAlbumArtists": "Artistas del \u00e1lbum:", @@ -1249,8 +1249,8 @@ "OptionSaveMetadataAsHidden": "Guardar metadatos e im\u00e1genes como archivos ocultos", "LabelExtractChaptersDuringLibraryScan": "Extraer im\u00e1genes de cap\u00edtulos durante el escaneo de la biblioteca", "LabelExtractChaptersDuringLibraryScanHelp": "Si se activa, las im\u00e1genes de cap\u00edtulos ser\u00e1n extra\u00eddas cuando los videos sean importados durante el escaneo de la biblioteca. Si se deshabilita, ser\u00e1n extra\u00eddas durante la ejecuci\u00f3n de la tarea programada de extracci\u00f3n de im\u00e1genes de cap\u00edtulos, permiti\u00e9ndo que el escaneo normal de la biblioteca se complete m\u00e1s r\u00e1pidamente.", - "LabelConnectGuestUserName": "Nombre de usuario o correo electr\u00f3nico de su invitado:", - "LabelConnectUserName": "Usuario\/correo electr\u00f3nico de Emby:", + "LabelConnectGuestUserName": "Su nombre de usuario Emby o correo electr\u00f3nico:", + "LabelConnectUserName": "Usuario de Emby\/correo electr\u00f3nico:", "LabelConnectUserNameHelp": "Conectar este usuario a una cuenta Emby para habilitar el ingreso f\u00e1cil desde cualquier aplicaci\u00f3n Emby sin tener que conocer la direcci\u00f3n ip del servidor.", "ButtonLearnMoreAboutEmbyConnect": "Conocer mas acerca de Emby Connect", "LabelExternalPlayers": "Reproductores Externos:", @@ -1300,7 +1300,7 @@ "TitleDevices": "Dispositivos", "TabCameraUpload": "Subir desde la C\u00e1mara", "TabDevices": "Dispositivos", - "HeaderCameraUploadHelp": "Subir autom\u00e1ticamente fotograf\u00edas y videos tomadas desde sus dispositivos m\u00f3viles a Emby.", + "HeaderCameraUploadHelp": "Subir autom\u00e1ticamente fotograf\u00edas y videos tomados desde sus dispositivos m\u00f3viles a Emby.", "MessageNoDevicesSupportCameraUpload": "Actualmente no cuenta con ning\u00fan dispositivo que soporte subir desde la c\u00e1mara.", "LabelCameraUploadPath": "Ruta para subir desde la c\u00e1mara:", "LabelCameraUploadPathHelp": "Seleccione una trayectoria personalizada de subida. Si no se especifica, una carpeta por omisi\u00f3n ser\u00e1 usada. Si usa una trayectoria personalizada, tambi\u00e9n ser\u00e1 necesario agregarla en el \u00e1rea de configuraci\u00f3n de la biblioteca.", @@ -1312,7 +1312,7 @@ "LabelConnectGuestUserNameHelp": "Este es el nombre de usuario que su amigo usa para accesar a la pagina web de Emby, o su direcci\u00f3n de correo electr\u00f3nico.", "HeaderInviteUserHelp": "Compartir sus medios con amigos es mas f\u00e1cil que nunca con Emby Connect.", "ButtonSendInvitation": "Enviar invitaci\u00f3n", - "HeaderSignInWithConnect": "Accesar con Emby Connect", + "HeaderSignInWithConnect": "Iniciar Sesi\u00f3n con Emby Connect", "HeaderGuests": "Invitados", "HeaderLocalUsers": "Usuarios Locales", "HeaderPendingInvitations": "Invitaciones Pendientes", @@ -1351,7 +1351,7 @@ "HeaderShareMediaFolders": "Compartir Carpetas de Medios", "MessageGuestSharingPermissionsHelp": "Muchas de las caracter\u00edsticas no est\u00e1n disponibles inicialmente para invitados pero pueden ser activadas conforme se necesiten.", "HeaderInvitations": "Invitaciones", - "LabelForgotPasswordUsernameHelp": "Ingresa tu nombre de usuario, si lo recuerdas.", + "LabelForgotPasswordUsernameHelp": "Introduce tu nombre de usuario, si lo recuerdas.", "HeaderForgotPassword": "Contrase\u00f1a Olvidada", "TitleForgotPassword": "Contrase\u00f1a Olvidada", "TitlePasswordReset": "Restablecer Contrase\u00f1a", @@ -1380,9 +1380,11 @@ "MessageReenableUser": "Vea abajo para volverlo a habilitar", "LabelEnableInternetMetadataForTvPrograms": "Descargar metadatos de Internet para:", "OptionTVMovies": "Pel\u00edculas de TV", - "HeaderUpcomingMovies": "Pr\u00f3ximas Pel\u00edculas", - "HeaderUpcomingPrograms": "Pr\u00f3ximos Programas", + "HeaderUpcomingMovies": "Pel\u00edculas por Estrenar", + "HeaderUpcomingPrograms": "Programas por Estrenar", "ButtonMoreItems": "M\u00e1s...", "LabelShowLibraryTileNames": "Mostrar nombres de t\u00edtulo de las bibliotecas", - "LabelShowLibraryTileNamesHelp": "Determina si se desplegar\u00e1n etiquetas debajo de los t\u00edtulos de las bibliotecas con la p\u00e1gina principal" + "LabelShowLibraryTileNamesHelp": "Determina si se desplegar\u00e1n etiquetas debajo de los t\u00edtulos de las bibliotecas con la p\u00e1gina principal", + "OptionEnableTranscodingThrottle": "Enable throttling", + "OptionEnableTranscodingThrottleHelp": "Throttling will automatically adjust transcoding speed in order to minimize server cpu utilization during playback." } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/fi.json b/MediaBrowser.Server.Implementations/Localization/Server/fi.json index a37fb1fb68..4e7d9b5523 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/fi.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/fi.json @@ -1384,5 +1384,7 @@ "HeaderUpcomingPrograms": "Upcoming Programs", "ButtonMoreItems": "More...", "LabelShowLibraryTileNames": "Show library tile names", - "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page" + "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page", + "OptionEnableTranscodingThrottle": "Enable throttling", + "OptionEnableTranscodingThrottleHelp": "Throttling will automatically adjust transcoding speed in order to minimize server cpu utilization during playback." } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/fr.json b/MediaBrowser.Server.Implementations/Localization/Server/fr.json index 466905e1fe..9b7a58996a 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/fr.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/fr.json @@ -47,7 +47,7 @@ "LabelDashboardSourcePathHelp": "Si vous ex\u00e9cutez le serveur \u00e0 partir des sources, veuillez sp\u00e9cifier le chemin du r\u00e9pertoire dashboard-ui. Tous les fichiers du client web seront servis \u00e0 partir de cet endroit.", "ButtonConvertMedia": "Convertir le m\u00e9dia", "ButtonOrganize": "Organiser", - "LinkedToEmbyConnect": "Connect\u00e9 \u00e0 Elmby", + "LinkedToEmbyConnect": "Connect\u00e9 \u00e0 Emby", "HeaderSupporterBenefits": "Avantages de supporteur", "HeaderAddUser": "Ajouter un utilisateur", "LabelAddConnectSupporterHelp": "Pour ajouter un utilisateur non list\u00e9, vous devrez d'abord lier son compte \u00e0 Emby Connect depuis sa page de profil utilisateur.", @@ -108,7 +108,7 @@ "LabelCountry": "Pays:", "LabelLanguage": "Langue:", "LabelTimeLimitHours": "Limite de temps (heures) :", - "ButtonJoinTheDevelopmentTeam": "Joignez-vous \u00e0 l'\u00e9quipe de d\u00e9veloppement", + "ButtonJoinTheDevelopmentTeam": "Rejoignez l'\u00e9quipe de d\u00e9veloppement", "HeaderPreferredMetadataLanguage": "Langue pr\u00e9f\u00e9r\u00e9e pour les m\u00e9tadonn\u00e9es:", "LabelSaveLocalMetadata": "Enregistrer les images et m\u00e9tadonn\u00e9es dans les r\u00e9pertoires de m\u00e9dia", "LabelSaveLocalMetadataHelp": "L'enregistrement des images et des m\u00e9tadonn\u00e9es dans le r\u00e9pertoire de m\u00e9dia les placera \u00e0 un endroit o\u00f9 elles seront facilement modifiables.", @@ -180,7 +180,7 @@ "OptionFavorite": "Favoris", "OptionLikes": "Aim\u00e9s", "OptionDislikes": "Non aim\u00e9s", - "OptionActors": "Acteur(e)s", + "OptionActors": "Acteur(trice)s", "OptionGuestStars": "Invit\u00e9s sp\u00e9ciaux", "OptionDirectors": "R\u00e9alisateurs", "OptionWriters": "Auteur(e)s", @@ -1384,5 +1384,7 @@ "HeaderUpcomingPrograms": "Programmes \u00e0 venir", "ButtonMoreItems": "Plus...", "LabelShowLibraryTileNames": "Voir les noms des affiches de la biblioth\u00e8que", - "LabelShowLibraryTileNamesHelp": "D\u00e9termine si les noms doivent \u00eatre affich\u00e9s en dessous des affiches de la biblioth\u00e8que sur la page d'accueil" + "LabelShowLibraryTileNamesHelp": "D\u00e9termine si les noms doivent \u00eatre affich\u00e9s en dessous des affiches de la biblioth\u00e8que sur la page d'accueil", + "OptionEnableTranscodingThrottle": "Enable throttling", + "OptionEnableTranscodingThrottleHelp": "Throttling will automatically adjust transcoding speed in order to minimize server cpu utilization during playback." } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/he.json b/MediaBrowser.Server.Implementations/Localization/Server/he.json index 3e2be090c7..6ccbee4a71 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/he.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/he.json @@ -1384,5 +1384,7 @@ "HeaderUpcomingPrograms": "Upcoming Programs", "ButtonMoreItems": "More...", "LabelShowLibraryTileNames": "Show library tile names", - "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page" + "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page", + "OptionEnableTranscodingThrottle": "Enable throttling", + "OptionEnableTranscodingThrottleHelp": "Throttling will automatically adjust transcoding speed in order to minimize server cpu utilization during playback." } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/hr.json b/MediaBrowser.Server.Implementations/Localization/Server/hr.json index bb407e2131..c9de0acda0 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/hr.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/hr.json @@ -1384,5 +1384,7 @@ "HeaderUpcomingPrograms": "Upcoming Programs", "ButtonMoreItems": "More...", "LabelShowLibraryTileNames": "Show library tile names", - "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page" + "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page", + "OptionEnableTranscodingThrottle": "Enable throttling", + "OptionEnableTranscodingThrottleHelp": "Throttling will automatically adjust transcoding speed in order to minimize server cpu utilization during playback." } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/it.json b/MediaBrowser.Server.Implementations/Localization/Server/it.json index 07f3a2b3ba..79be970d61 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/it.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/it.json @@ -7,7 +7,7 @@ "LabelApiDocumentation": "Documentazione Api", "LabelDeveloperResources": "Risorse programmatori", "LabelBrowseLibrary": "Esplora la libreria", - "LabelConfigureServer": "Configure Emby", + "LabelConfigureServer": "Configura Emby", "LabelOpenLibraryViewer": "Apri visualizzatore libreria", "LabelRestartServer": "Riavvia Server", "LabelShowLogWindow": "Mostra Finestra log", @@ -15,7 +15,7 @@ "LabelFinish": "Finito", "LabelNext": "Prossimo", "LabelYoureDone": "Hai Finito!", - "WelcomeToProject": "Welcome to Emby!", + "WelcomeToProject": "Benvenuto in Emby", "ThisWizardWillGuideYou": "Questa procedura ti guider\u00e0 durante il processo di installazione. Per cominciare, per favore seleziona la tua lingua preferita", "TellUsAboutYourself": "Parlaci di te", "ButtonQuickStartGuide": "Guida rapida", @@ -34,7 +34,7 @@ "LabelChapterImageExtractionForMoviesHelp": "L'estrazione delle immagini dai capitoli permetter\u00e0 ai client di avere un men\u00f9 grafico per la selezione delle scene. Il processo potrebbe essere lento, con uso intensivo della CPU e potrebbe richiedere diversi gigabyte di spazio. Viene avviato durante la notte, ad ogni modo \u00e8 configurabile nella sezione azioni pianificate. Non \u00e8 raccomandato l'avvio di questo processo durante le ore di massimo utilizzo.", "LabelEnableAutomaticPortMapping": "Abilita mappatura delle porte automatiche", "LabelEnableAutomaticPortMappingHelp": "UPnP consente la configurazione automatica del router per l'accesso remoto facile. Questo potrebbe non funzionare con alcuni modelli di router.", - "HeaderTermsOfService": "Emby Terms of Service", + "HeaderTermsOfService": "Emby termini di servizio", "MessagePleaseAcceptTermsOfService": "Per favore accetta i termini di servizio e l'informativa sulla privacy prima di continuare.", "OptionIAcceptTermsOfService": "Accetto i termini di servizio", "ButtonPrivacyPolicy": "Informativa sulla privacy", @@ -47,7 +47,7 @@ "LabelDashboardSourcePathHelp": "se si sta eseguendo il server da una sorgente, specifica il percorso dell'interfaccia. Tutti i file per i client saranno presi da questo percorso", "ButtonConvertMedia": "Converti media", "ButtonOrganize": "Organizza", - "LinkedToEmbyConnect": "Linked to Emby Connect", + "LinkedToEmbyConnect": "Collegato a Emby Connect", "HeaderSupporterBenefits": "Supporter Benefits", "HeaderAddUser": "Add User", "LabelAddConnectSupporterHelp": "To add a user who isn't listed, you'll need to first link their account to Emby Connect from their user profile page.", @@ -1384,5 +1384,7 @@ "HeaderUpcomingPrograms": "Upcoming Programs", "ButtonMoreItems": "Pi\u00f9...", "LabelShowLibraryTileNames": "Show library tile names", - "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page" + "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page", + "OptionEnableTranscodingThrottle": "Enable throttling", + "OptionEnableTranscodingThrottleHelp": "Throttling will automatically adjust transcoding speed in order to minimize server cpu utilization during playback." } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/kk.json b/MediaBrowser.Server.Implementations/Localization/Server/kk.json index 7a5f88c33f..0f9ae2bed9 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/kk.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/kk.json @@ -683,7 +683,7 @@ "NotificationOptionInstallationFailed": "\u041e\u0440\u043d\u0430\u0442\u0443 \u0441\u04d9\u0442\u0441\u0456\u0437\u0434\u0456\u0433\u0456", "NotificationOptionNewLibraryContent": "\u0416\u0430\u04a3\u0430 \u043c\u0430\u0437\u043c\u04b1\u043d \u04af\u0441\u0442\u0435\u043b\u0433\u0435\u043d", "NotificationOptionNewLibraryContentMultiple": "\u0416\u0430\u04a3\u0430 \u043c\u0430\u0437\u043c\u04b1\u043d \u049b\u043e\u0441\u044b\u043b\u0434\u044b (\u043a\u04e9\u043f\u0442\u0435\u0433\u0435\u043d)", - "NotificationOptionCameraImageUploaded": "\u041a\u0430\u043c\u0435\u0440\u0430\u0434\u0430\u043d \u0444\u043e\u0442\u043e\u0441\u0443\u0440\u0435\u0442 \u0436\u04af\u043a\u0442\u0435\u043b\u0433\u0435\u043d", + "NotificationOptionCameraImageUploaded": "\u041a\u0430\u043c\u0435\u0440\u0430\u0434\u0430\u043d \u0444\u043e\u0442\u043e\u0441\u0443\u0440\u0435\u0442 \u043a\u0435\u0440\u0456 \u049b\u043e\u0442\u0430\u0440\u044b\u043b\u0493\u0430\u043d", "NotificationOptionUserLockedOut": "\u041f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u0443\u0448\u044b \u049b\u04b1\u0440\u0441\u0430\u0443\u043b\u044b", "HeaderSendNotificationHelp": "\u04d8\u0434\u0435\u043f\u043a\u0456\u0434\u0435, \u0445\u0430\u0431\u0430\u0440\u043b\u0430\u043d\u0434\u044b\u0440\u0443\u043b\u0430\u0440 \u0431\u0430\u049b\u044b\u043b\u0430\u0443 \u0442\u0430\u049b\u0442\u0430\u04a3\u044b\u0437\u0434\u0430\u0493\u044b \u043a\u0456\u0440\u0456\u0441 \u0436\u04d9\u0448\u0456\u0433\u0456\u043d\u0435 \u0436\u0435\u0442\u043a\u0456\u0437\u0456\u043b\u0435\u0434\u0456. \u049a\u043e\u0441\u044b\u043c\u0448\u0430 \u0445\u0430\u0431\u0430\u0440\u043b\u0430\u043d\u0434\u044b\u0440\u0443 \u043c\u04af\u043c\u043a\u0456\u043d\u0434\u0456\u043a\u0442\u0435\u0440\u0456\u043d \u043e\u0440\u043d\u0430\u0442\u0443 \u04af\u0448\u0456\u043d \u043f\u043b\u0430\u0433\u0438\u043d\u0434\u0435\u0440 \u0442\u0456\u0437\u0456\u043c\u0434\u0435\u043c\u0435\u0441\u0456\u043d \u0448\u043e\u043b\u044b\u04a3\u044b\u0437.", "NotificationOptionServerRestartRequired": "\u0421\u0435\u0440\u0432\u0435\u0440\u0434\u0456 \u049b\u0430\u0439\u0442\u0430 \u0456\u0441\u043a\u0435 \u049b\u043e\u0441\u0443 \u049b\u0430\u0436\u0435\u0442", @@ -1125,7 +1125,7 @@ "UserDownloadingItemWithValues": "{0} \u043c\u044b\u043d\u0430\u043d\u044b \u0436\u04af\u043a\u0442\u0435\u043f \u0430\u043b\u0443\u0434\u0430: {1}", "UserStartedPlayingItemWithValues": "{0} - {1} \u043e\u0439\u043d\u0430\u0442\u0443\u044b \u0431\u0430\u0441\u0442\u0430\u043b\u0434\u044b", "UserStoppedPlayingItemWithValues": "{0} - {1} \u043e\u0439\u043d\u0430\u0442\u0443\u044b \u0442\u043e\u049b\u0442\u0430\u043b\u0434\u044b", - "AppDeviceValues": "\u049a\u043e\u043b\u0434\u0430\u043d\u0431\u0430: {0}, \u0416\u0430\u0431\u0434\u044b\u049b: {1}", + "AppDeviceValues": "\u049a\u043e\u043b\u0434\u0430\u043d\u0431\u0430: {0}, \u049a\u04b1\u0440\u044b\u043b\u0493\u044b: {1}", "ProviderValue": "\u0416\u0435\u0442\u043a\u0456\u0437\u0443\u0448\u0456: {0}", "LabelChannelDownloadSizeLimit": "\u0416\u04af\u043a\u0442\u0435\u043c\u0435 \u04e9\u043b\u0448\u0435\u043c\u0456\u043d\u0456\u04a3 \u0448\u0435\u0433\u0456 (GB)", "LabelChannelDownloadSizeLimitHelpText": "\u0410\u0440\u043d\u0430\u043b\u0430\u0440\u0434\u044b \u049b\u043e\u0442\u0430\u0440\u044b\u043f \u0430\u043b\u0443 \u04af\u0448\u0456\u043d \u049b\u0430\u043b\u0442\u0430\u0441\u044b \u04e9\u043b\u0448\u0435\u043c\u0456\u043d \u0448\u0435\u043a\u0442\u0435\u0443.", @@ -1298,14 +1298,14 @@ "LabelDateAddedBehaviorHelp": "\u0415\u0433\u0435\u0440 \u043c\u0435\u0442\u0430\u0434\u0435\u0440\u0435\u043a\u0442\u0435\u0440\u0434\u0435 \u043c\u04d9\u043d\u0456 \u0431\u043e\u043b\u0441\u0430, \u0431\u04b1\u043b \u049b\u0430\u0439\u0441\u044b\u0431\u0456\u0440 \u043e\u0441\u044b \u043d\u04b1\u0441\u049b\u0430\u043b\u0430\u0440\u0434\u044b\u04a3 \u0430\u043b\u0434\u044b\u043d\u0434\u0430 \u04d9\u0440\u049b\u0430\u0448\u0430\u043d\u0434\u0430 \u043f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u044b\u043b\u0430\u0434\u044b.", "LabelNumberTrailerToPlay": "\u0422\u0440\u0435\u0439\u043b\u0435\u0440\u0434\u0456\u04a3 \u043e\u0439\u043d\u0430\u0442\u044b\u043b\u0443 \u04af\u0448\u0456\u043d \u0441\u0430\u043d\u044b:", "TitleDevices": "\u049a\u04b1\u0440\u044b\u043b\u0493\u044b\u043b\u0430\u0440", - "TabCameraUpload": "\u041a\u0430\u043c\u0435\u0440\u0430\u0434\u0430\u043d \u043a\u0435\u0440\u0456 \u049b\u043e\u0442\u0430\u0440\u0443", + "TabCameraUpload": "\u041a\u0430\u043c\u0435\u0440\u0430\u043b\u0430\u0440", "TabDevices": "\u049a\u04b1\u0440\u044b\u043b\u0493\u044b\u043b\u0430\u0440", - "HeaderCameraUploadHelp": "\u04b0\u0442\u049b\u044b\u0440 \u049b\u04b1\u0440\u044b\u043b\u0493\u044b\u043b\u0430\u0440\u044b\u04a3\u044b\u0437\u0431\u0435\u043d \u0442\u04af\u0441\u0456\u0440\u0456\u043b\u0433\u0435\u043d \u0444\u043e\u0442\u043e\u0441\u0443\u0440\u0435\u0442\u0442\u0435\u0440 \u043c\u0435\u043d \u0431\u0435\u0439\u043d\u0435\u0444\u0430\u0439\u043b\u0434\u0430\u0440\u0434\u044b Emby \u0456\u0448\u0456\u043d\u0435 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0442\u044b \u0442\u04af\u0440\u0434\u0435 \u049b\u043e\u0442\u0430\u0440\u044b\u043f \u0431\u0435\u0440\u0443.", - "MessageNoDevicesSupportCameraUpload": "\u0410\u0493\u044b\u043c\u0434\u0430 \u043a\u0430\u043c\u0435\u0440\u0430\u0434\u0430\u043d \u049b\u043e\u0442\u0430\u0440\u044b\u043f \u0431\u0435\u0440\u0435\u0442\u0456\u043d \u0435\u0448\u049b\u0430\u043d\u0434\u0430\u0439 \u049b\u04b1\u0440\u044b\u043b\u0493\u044b\u043b\u0430\u0440\u044b\u04a3\u044b\u0437 \u0436\u043e\u049b.", + "HeaderCameraUploadHelp": "\u04b0\u0442\u049b\u044b\u0440 \u049b\u04b1\u0440\u044b\u043b\u0493\u044b\u043b\u0430\u0440\u044b\u04a3\u044b\u0437\u0431\u0435\u043d \u0442\u04af\u0441\u0456\u0440\u0456\u043b\u0433\u0435\u043d \u0444\u043e\u0442\u043e\u0441\u0443\u0440\u0435\u0442\u0442\u0435\u0440 \u043c\u0435\u043d \u0431\u0435\u0439\u043d\u0435\u0444\u0430\u0439\u043b\u0434\u0430\u0440\u0434\u044b Emby \u0456\u0448\u0456\u043d\u0435 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0442\u044b \u0442\u04af\u0440\u0434\u0435 \u043a\u0435\u0440\u0456 \u049b\u043e\u0442\u0430\u0440\u0443.", + "MessageNoDevicesSupportCameraUpload": "\u0410\u0493\u044b\u043c\u0434\u0430 \u043a\u0430\u043c\u0435\u0440\u0430\u0434\u0430\u043d \u043a\u0435\u0440\u0456 \u049b\u043e\u0442\u0430\u0440\u044b\u043f \u0431\u0435\u0440\u0435\u0442\u0456\u043d \u0435\u0448\u049b\u0430\u043d\u0434\u0430\u0439 \u049b\u04b1\u0440\u044b\u043b\u0493\u044b\u043b\u0430\u0440\u044b\u04a3\u044b\u0437 \u0436\u043e\u049b.", "LabelCameraUploadPath": "\u041a\u0430\u043c\u0435\u0440\u0430\u0434\u0430\u043d \u043a\u0435\u0440\u0456 \u049b\u043e\u0442\u0430\u0440\u0443 \u0436\u043e\u043b\u044b:", - "LabelCameraUploadPathHelp": "\u049a\u0430\u043b\u0430\u0443\u044b\u04a3\u044b\u0437 \u0431\u043e\u0439\u044b\u043d\u0448\u0430 \u0442\u0435\u04a3\u0448\u0435\u043b\u0433\u0435\u043d \u0436\u043e\u043b\u0434\u044b \u0442\u0430\u04a3\u0434\u0430\u04a3\u044b\u0437. \u0415\u0433\u0435\u0440 \u0430\u043d\u044b\u049b\u0442\u0430\u043b\u043c\u0430\u0441\u0430, \u04d9\u0434\u0435\u043f\u043a\u0456 \u049b\u0430\u043b\u0442\u0430 \u043f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u044b\u043b\u0430\u0434\u044b. \u0415\u0433\u0435\u0440 \u0442\u0435\u04a3\u0448\u0435\u043b\u0435\u0442\u0456\u043d \u0436\u043e\u043b \u043f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u044b\u043b\u0441\u0430, \u0431\u04b1\u043d\u044b \u0441\u043e\u043d\u0434\u0430\u0439-\u0430\u049b \u0422\u0430\u0441\u044b\u0493\u044b\u0448\u0445\u0430\u043d\u0430\u043d\u044b \u043e\u0440\u043d\u0430\u0442\u0443 \u0436\u04d9\u043d\u0435 \u0442\u0435\u04a3\u0448\u0435\u0443 \u0430\u0439\u043c\u0430\u0493\u044b\u043d\u0430 \u04af\u0441\u0442\u0435\u0443 \u049b\u0430\u0436\u0435\u0442.", + "LabelCameraUploadPathHelp": "\u049a\u0430\u043b\u0430\u0443\u044b\u04a3\u044b\u0437 \u0431\u043e\u0439\u044b\u043d\u0448\u0430 \u0442\u0435\u04a3\u0448\u0435\u043b\u0433\u0435\u043d \u043a\u0435\u0440\u0456 \u049b\u043e\u0442\u0430\u0440\u0443 \u0436\u043e\u043b\u0434\u044b \u0442\u0430\u04a3\u0434\u0430\u04a3\u044b\u0437. \u0415\u0433\u0435\u0440 \u0430\u043d\u044b\u049b\u0442\u0430\u043b\u043c\u0430\u0441\u0430, \u04d9\u0434\u0435\u043f\u043a\u0456 \u049b\u0430\u043b\u0442\u0430 \u043f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u044b\u043b\u0430\u0434\u044b. \u0415\u0433\u0435\u0440 \u0442\u0435\u04a3\u0448\u0435\u043b\u0435\u0442\u0456\u043d \u0436\u043e\u043b \u043f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u044b\u043b\u0441\u0430, \u0431\u04b1\u043d\u044b \u0441\u043e\u043d\u0434\u0430\u0439-\u0430\u049b \u0422\u0430\u0441\u044b\u0493\u044b\u0448\u0445\u0430\u043d\u0430\u043d\u044b \u043e\u0440\u043d\u0430\u0442\u0443 \u0436\u04d9\u043d\u0435 \u0442\u0435\u04a3\u0448\u0435\u0443 \u0430\u0439\u043c\u0430\u0493\u044b\u043d\u0430 \u04af\u0441\u0442\u0435\u0443 \u049b\u0430\u0436\u0435\u0442.", "LabelCreateCameraUploadSubfolder": "\u04d8\u0440\u049b\u0430\u0439\u0441\u044b \u049b\u04b1\u0440\u044b\u043b\u0493\u044b \u04af\u0448\u0456\u043d \u0456\u0448\u043a\u0456 \u049b\u0430\u043b\u0442\u0430 \u0436\u0430\u0441\u0430\u0443", - "LabelCreateCameraUploadSubfolderHelp": "\u0416\u0430\u0431\u0434\u044b\u049b\u0442\u0430\u0440 \u0431\u0435\u0442\u0456\u043d\u0434\u0435 \u043d\u04b1\u049b\u044b\u0493\u0430\u043d\u0434\u0430 \u049b\u04b1\u0440\u044b\u043b\u0493\u044b\u0493\u0430 \u043d\u0430\u049b\u0442\u044b \u049b\u0430\u043b\u0442\u0430\u043b\u0430\u0440 \u0442\u0430\u0493\u0430\u0439\u044b\u043d\u0434\u0430\u043b\u0443\u044b \u043c\u04af\u043c\u043a\u0456\u043d.", + "LabelCreateCameraUploadSubfolderHelp": "\u049a\u04b1\u0440\u044b\u043b\u0493\u044b\u043b\u0430\u0440 \u0431\u0435\u0442\u0456\u043d\u0434\u0435 \u043d\u04b1\u049b\u044b\u0493\u0430\u043d\u0434\u0430 \u049b\u04b1\u0440\u044b\u043b\u0493\u044b\u0493\u0430 \u043d\u0430\u049b\u0442\u044b \u049b\u0430\u043b\u0442\u0430\u043b\u0430\u0440 \u0442\u0430\u0493\u0430\u0439\u044b\u043d\u0434\u0430\u043b\u0443\u044b \u043c\u04af\u043c\u043a\u0456\u043d.", "LabelCustomDeviceDisplayName": "\u0411\u0435\u0439\u043d\u0435\u043b\u0435\u043d\u0443 \u0430\u0442\u044b:", "LabelCustomDeviceDisplayNameHelp": "\u0411\u0435\u0439\u043d\u0435\u043b\u0435\u043d\u0435\u0442\u0456\u043d \u0442\u0435\u04a3\u0448\u0435\u043b\u0433\u0435\u043d \u0430\u0442\u044b\u043d \u04b1\u0441\u044b\u043d\u044b\u04a3\u044b\u0437 \u043d\u0435\u043c\u0435\u0441\u0435 \u049b\u04b1\u0440\u044b\u043b\u0493\u044b \u0430\u0440\u049b\u044b\u043b\u044b \u0431\u0430\u044f\u043d\u0434\u0430\u043b\u0493\u0430\u043d \u0430\u0442\u044b\u043d \u043f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u0443 \u04af\u0448\u0456\u043d \u0431\u043e\u0441 \u049b\u0430\u043b\u0434\u044b\u0440\u044b\u04a3\u044b\u0437.", "HeaderInviteUser": "\u041f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u0443\u0448\u044b\u043d\u044b \u0448\u0430\u049b\u044b\u0440\u0443", @@ -1384,5 +1384,7 @@ "HeaderUpcomingPrograms": "\u041a\u04af\u0442\u0456\u043b\u0433\u0435\u043d \u0431\u0435\u0440\u043b\u0456\u043c\u0434\u0435\u0440", "ButtonMoreItems": "\u041a\u04e9\u0431\u0456\u0440\u0435\u043a...", "LabelShowLibraryTileNames": "\u0422\u0430\u0441\u044b\u0493\u044b\u0448\u0445\u0430\u043d\u0430 \u0442\u0430\u049b\u0442\u0430\u0439\u0448\u0430\u043b\u0430\u0440\u044b\u043d\u044b\u04a3 \u0430\u0442\u0430\u0443\u043b\u0430\u0440\u044b\u043d \u043a\u04e9\u0440\u0441\u0435\u0442\u0443", - "LabelShowLibraryTileNamesHelp": "\u0411\u0430\u0441\u0442\u044b \u0431\u0435\u0442\u0442\u0435 \u0442\u0430\u0441\u044b\u0493\u044b\u0448\u0445\u0430\u043d\u0430 \u0442\u0430\u049b\u0442\u0430\u0439\u0448\u0430\u043b\u0430\u0440\u044b \u0430\u0441\u0442\u044b\u043d\u0434\u0430 \u0436\u0430\u0437\u0443\u043b\u0430\u0440 \u043a\u04e9\u0440\u0441\u0435\u0442\u0456\u043b\u0435 \u043c\u0435 \u0435\u043a\u0435\u043d\u0456 \u0430\u043d\u044b\u049b\u0442\u0430\u043b\u0430\u0434\u044b." + "LabelShowLibraryTileNamesHelp": "\u0411\u0430\u0441\u0442\u044b \u0431\u0435\u0442\u0442\u0435 \u0442\u0430\u0441\u044b\u0493\u044b\u0448\u0445\u0430\u043d\u0430 \u0442\u0430\u049b\u0442\u0430\u0439\u0448\u0430\u043b\u0430\u0440\u044b \u0430\u0441\u0442\u044b\u043d\u0434\u0430 \u0436\u0430\u0437\u0443\u043b\u0430\u0440 \u043a\u04e9\u0440\u0441\u0435\u0442\u0456\u043b\u0435 \u043c\u0435 \u0435\u043a\u0435\u043d\u0456 \u0430\u043d\u044b\u049b\u0442\u0430\u043b\u0430\u0434\u044b.", + "OptionEnableTranscodingThrottle": "\u0420\u0435\u0442\u0442\u0435\u0443\u0434\u0456 \u049b\u043e\u0441\u0443", + "OptionEnableTranscodingThrottleHelp": "\u041e\u0439\u043d\u0430\u0442\u0443 \u043a\u0435\u0437\u0456\u043d\u0434\u0435 \u0441\u0435\u0440\u0432\u0435\u0440\u0434\u0456\u04a3 \u043e\u0440\u0442\u0430\u043b\u044b\u049b \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u0440\u044b\u043d \u043f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u0443\u044b\u043d \u0430\u0437\u0430\u0439\u0442\u0443 \u04af\u0448\u0456\u043d \u0440\u0435\u0442\u0442\u0435\u0443 \u049b\u0430\u0439\u0442\u0430 \u043a\u043e\u0434\u0442\u0430\u0443 \u049b\u0430\u0440\u049b\u044b\u043d\u044b\u043d \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0442\u044b \u0442\u04af\u0440\u0434\u0435 \u043b\u0430\u0439\u044b\u049b\u0442\u0430\u0439\u0434\u044b." } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/ko.json b/MediaBrowser.Server.Implementations/Localization/Server/ko.json index 7f852e4e68..36003bc3f6 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/ko.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/ko.json @@ -1384,5 +1384,7 @@ "HeaderUpcomingPrograms": "Upcoming Programs", "ButtonMoreItems": "More...", "LabelShowLibraryTileNames": "Show library tile names", - "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page" + "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page", + "OptionEnableTranscodingThrottle": "Enable throttling", + "OptionEnableTranscodingThrottleHelp": "Throttling will automatically adjust transcoding speed in order to minimize server cpu utilization during playback." } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/ms.json b/MediaBrowser.Server.Implementations/Localization/Server/ms.json index 3442210c72..12081cc5af 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/ms.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/ms.json @@ -1384,5 +1384,7 @@ "HeaderUpcomingPrograms": "Upcoming Programs", "ButtonMoreItems": "More...", "LabelShowLibraryTileNames": "Show library tile names", - "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page" + "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page", + "OptionEnableTranscodingThrottle": "Enable throttling", + "OptionEnableTranscodingThrottleHelp": "Throttling will automatically adjust transcoding speed in order to minimize server cpu utilization during playback." } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/nb.json b/MediaBrowser.Server.Implementations/Localization/Server/nb.json index 1c73a95f9f..cce3ec647a 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/nb.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/nb.json @@ -1384,5 +1384,7 @@ "HeaderUpcomingPrograms": "Upcoming Programs", "ButtonMoreItems": "Mer...", "LabelShowLibraryTileNames": "Show library tile names", - "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page" + "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page", + "OptionEnableTranscodingThrottle": "Enable throttling", + "OptionEnableTranscodingThrottleHelp": "Throttling will automatically adjust transcoding speed in order to minimize server cpu utilization during playback." } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/nl.json b/MediaBrowser.Server.Implementations/Localization/Server/nl.json index f2c0fe749e..ded3ae100b 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/nl.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/nl.json @@ -7,7 +7,7 @@ "LabelApiDocumentation": "Api documentatie", "LabelDeveloperResources": "Ontwikkelaars bronnen", "LabelBrowseLibrary": "Bekijk bibliotheek", - "LabelConfigureServer": "Configure Emby", + "LabelConfigureServer": "Emby Configureren", "LabelOpenLibraryViewer": "Open bibliotheek verkenner", "LabelRestartServer": "Server herstarten", "LabelShowLogWindow": "Toon log venster", @@ -15,18 +15,18 @@ "LabelFinish": "Voltooien", "LabelNext": "Volgende", "LabelYoureDone": "Gereed!", - "WelcomeToProject": "Welcome to Emby!", + "WelcomeToProject": "Welkom bij Emby!", "ThisWizardWillGuideYou": "Deze wizard helpt u door het setup-proces.", "TellUsAboutYourself": "Vertel ons over uzelf", "ButtonQuickStartGuide": "Snel start gids", "LabelYourFirstName": "Uw voornaam:", "MoreUsersCanBeAddedLater": "Meer gebruikers kunnen later via het dashboard worden toegevoegd.", - "UserProfilesIntro": "Emby includes built-in support for user profiles, enabling each user to have their own display settings, playstate and parental controls.", + "UserProfilesIntro": "Emby heeft ingebouwde ondersteuning voor gebruikersprofielen die het mogelijk maken om elke gebruiker eigen scherminstellingen, afspeelinstellingen en ouderlijk toezicht te geven.", "LabelWindowsService": "Windows Service", "AWindowsServiceHasBeenInstalled": "Er is een Windows service ge\u00efnstalleerd.", - "WindowsServiceIntro1": "Emby 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.", + "WindowsServiceIntro1": "Emby Server draait normaalgesproken als een desktop applicatie met een icoon in het systeemvaak, maar als u dat wilt kunt u het als een achtergrondproces draaien. Het kan daarvoor worden gestart vanuit het Windows Services configuratiescherm.", "WindowsServiceIntro2": "Wanneer u de Windows-service gebruikt, dan dient u er rekening mee te houden dat het niet op hetzelfde moment als de desktop applicatie kan worden uitgevoerd. Het is daarom vereist de desktop applicatie eerst af te sluiten voordat u de service gebruikt. De service moet worden geconfigureerd met beheerdersrechten via het configuratie scherm. Houd er rekening mee dat op dit moment de service niet automatisch kan worden bijgewerkt, zodat nieuwe versies dus handmatige interactie vereisen.", - "WizardCompleted": "That's all we need for now. Emby has begun collecting information about your media library. Check out some of our apps, and then click Finish<\/b> to view the Server Dashboard<\/b>.", + "WizardCompleted": "Dat is alles wat we nu nodig hebben. Emby is begonnen met het verzamelen van informatie over uw media bibliotheek. Probeer sommige van onze apps en klik dan Finish<\/b> om het Server Dashboard<\/b> te bekijken.", "LabelConfigureSettings": "Configureer instellingen", "LabelEnableVideoImageExtraction": "Videobeeld uitpakken inschakelen", "VideoImageExtractionHelp": "Voor video's die nog geen afbeeldingen hebben, en waarvoor geen afbeeldingen op Internet te vinden zijn. Dit voegt extra tijd toe aan de oorspronkelijke bibliotheek scan, maar resulteert in een mooiere weergave.", @@ -34,7 +34,7 @@ "LabelChapterImageExtractionForMoviesHelp": "Uitpakken van hoofdstuk afbeeldingen biedt clients grafische scene selectie menu's. Het proces kan langzaam en processor intensief zijn en kan enkele gigabytes aan vrije ruimte vereisen. Het draait 's nachts als geplande taak, hoewel dit aangepast kan worden bij de geplande taken. Het wordt niet aanbevolen om deze taak tijdens piekuren te draaien.", "LabelEnableAutomaticPortMapping": "Automatische poorttoewijzing inschakelen", "LabelEnableAutomaticPortMappingHelp": "UPnP zorgt voor geautomatiseerde configuratie van de router voor gemakkelijke toegang op afstand. Dit werkt mogelijk niet met sommige routers.", - "HeaderTermsOfService": "Emby Terms of Service", + "HeaderTermsOfService": "Emby Service Voorwaarden", "MessagePleaseAcceptTermsOfService": "Accepteer a.u.b. de voorwaarden en Privacybeleid voordat u doorgaat.", "OptionIAcceptTermsOfService": "Ik accepteer de voorwaarden", "ButtonPrivacyPolicy": "Privacybeleid", @@ -47,10 +47,10 @@ "LabelDashboardSourcePathHelp": "Wanneer u de server draait vanaf de bron, geeft u het pad naar de map dashboard-ui op. Alle web client bestanden worden geladen vanaf deze locatie.", "ButtonConvertMedia": "Converteer media", "ButtonOrganize": "Organiseren", - "LinkedToEmbyConnect": "Linked to Emby Connect", - "HeaderSupporterBenefits": "Supporter Benefits", - "HeaderAddUser": "Add User", - "LabelAddConnectSupporterHelp": "To add a user who isn't listed, you'll need to first link their account to Emby Connect from their user profile page.", + "LinkedToEmbyConnect": "Gekoppeld aan Emby Connect", + "HeaderSupporterBenefits": "Voordelen voor Supporters", + "HeaderAddUser": "Gebruiker Toevoegen", + "LabelAddConnectSupporterHelp": "Om een \u200b\u200bgebruiker toe te voegen die niet in de lijst voorkomt, moet je eerst hun account koppelen aan Emby Connect uit hun gebruikersprofiel pagina.", "LabelPinCode": "Pincode:", "ButtonOk": "Ok", "ButtonCancel": "Annuleren", @@ -61,13 +61,13 @@ "HeaderVideo": "Video", "HeaderPaths": "Paden", "CategorySync": "Sync", - "TabPlaylist": "Playlist", + "TabPlaylist": "Afspeellijst", "HeaderEasyPinCode": "Eenvoudige Pincode", "HeaderGrownupsOnly": "Alleen voor volwassenen!", "DividerOr": "-- of --", - "HeaderInstalledServices": "Installed Services", - "HeaderAvailableServices": "Available Services", - "MessageNoServicesInstalled": "No services are currently installed.", + "HeaderInstalledServices": "Ge\u00efnstalleerde diensten", + "HeaderAvailableServices": "Beschikbare diensten", + "MessageNoServicesInstalled": "Er zijn momenteel geen diensten ge\u00efnstalleerd.", "HeaderToAccessPleaseEnterEasyPinCode": "Voor toegang toets uw pincode", "KidsModeAdultInstruction": "Klik op het slotje in de rechterbenedenhoek om te configureren of blijf in de kindermodus. Uw pincode nodig zal zijn", "ButtonConfigurePinCode": "Configureer pincode", @@ -84,7 +84,7 @@ "OptionDetectArchiveFilesAsMedia": "Herken archief bestanden als media", "OptionDetectArchiveFilesAsMediaHelp": "Indien ingeschakeld zullen bestanden met .rar en .zip extensies herkend worden als media bestanden.", "LabelEnterConnectUserName": "Gebruikersnaam of e-mail:", - "LabelEnterConnectUserNameHelp": "This is your Emby online account user name or password.", + "LabelEnterConnectUserNameHelp": "Dit is uw Emby online account gebruikersnaam en wachtwoord.", "LabelEnableEnhancedMovies": "Verbeterde film displays inschakelen", "LabelEnableEnhancedMoviesHelp": "Wanneer ingeschakeld, zullen films worden weergegeven als mappen inclusief trailers, extra's, cast & crew en andere gerelateerde inhoud.", "HeaderSyncJobInfo": "Sync Opdrachten", @@ -113,7 +113,7 @@ "LabelSaveLocalMetadata": "Sla afbeeldingen en metadata op in de mediamappen", "LabelSaveLocalMetadataHelp": "Door afbeeldingen en metadata op te slaan in de mediamappen kunnen ze makkelijker worden gevonden en bewerkt.", "LabelDownloadInternetMetadata": "Download afbeeldingen en metadata van het internet", - "LabelDownloadInternetMetadataHelp": "Emby Server can download information about your media to enable rich presentations.", + "LabelDownloadInternetMetadataHelp": "Emby Server kan informatie downloaden van uw media om rijke presentaties mogelijk te maken.", "TabPreferences": "Voorkeuren", "TabPassword": "Wachtwoord", "TabLibraryAccess": "Bibliotheek toegang", @@ -136,7 +136,7 @@ "LabelAudioLanguagePreference": "Voorkeurs taal geluid:", "LabelSubtitleLanguagePreference": "Voorkeurs taal ondertiteling:", "OptionDefaultSubtitles": "Standaard", - "OptionOnlyForcedSubtitles": "Alleen 'geforceerde' ondertiteling", + "OptionOnlyForcedSubtitles": "Alleen geforceerde ondertiteling", "OptionAlwaysPlaySubtitles": "Ondertiteling altijd weergeven", "OptionNoSubtitles": "Geen ondertitels", "OptionDefaultSubtitlesHelp": "Ondertiteling wordt weergegeven in de voorkeurstaal als de audio in een andere taal is.", @@ -288,12 +288,12 @@ "TabAbout": "Over", "TabSupporterKey": "Supporter Sleutel", "TabBecomeSupporter": "Word Supporter", - "ProjectHasCommunity": "Emby has a thriving community of users and contributors.", - "CheckoutKnowledgeBase": "Check out our knowledge base to help you get the most out of Emby.", + "ProjectHasCommunity": "Emby heeft een bloeiende gemeenschap van gebruikers en medewerkers", + "CheckoutKnowledgeBase": "Bekijk onze knowledge base om u te helpen het meeste uit Emby halen.", "SearchKnowledgeBase": "Zoeken in de Kennisbank", "VisitTheCommunity": "Bezoek de Gemeenschap", - "VisitProjectWebsite": "Visit the Emby Web Site", - "VisitProjectWebsiteLong": "Visit the Emby Web site to catch the latest news and keep up with the developer blog.", + "VisitProjectWebsite": "Bezoek de Emby Website", + "VisitProjectWebsiteLong": "Bezoek de Emby Web-website voor het laatste nieuws en blijf op de hoogte via het ontwikkelaars blog.", "OptionHideUser": "Verberg deze gebruiker op de aanmeldschermen", "OptionHideUserFromLoginHelp": "Handig voor piv\u00e9 of verborgen beheer accounts. De gebruiker zal handmatig m.b.v. gebruikersnaam en wachtwoord aan moeten melden.", "OptionDisableUser": "Dit account uitschakelen", @@ -368,8 +368,8 @@ "LabelMetadataDownloadLanguage": "Voorkeurs taal:", "ButtonAutoScroll": "Auto-scroll", "LabelImageSavingConvention": "Afbeelding opslag conventie:", - "LabelImageSavingConventionHelp": "Emby recognizes images from most major media applications. Choosing your downloading convention is useful if you also use other products.", - "OptionImageSavingCompatible": "Compatible - Emby\/Kodi\/Plex", + "LabelImageSavingConventionHelp": "Emby herkent beelden van de meeste grote media-applicaties Het kiezen van uw download conventie is handig als u ook gebruik maakt van andere producten.", + "OptionImageSavingCompatible": "Compatible - Empty\/Kodi\/Plex", "OptionImageSavingStandard": "Standaard - MB2", "ButtonSignIn": "Aanmelden", "TitleSignIn": "Aanmelden", @@ -419,7 +419,7 @@ "LabelNumberOfGuideDays": "Aantal dagen van de gids om te downloaden:", "LabelNumberOfGuideDaysHelp": "Het downloaden van meer dagen van de gids gegevens biedt de mogelijkheid verder vooruit te plannen en een beter overzicht geven, maar het zal ook langer duren om te downloaden. Auto kiest op basis van het aantal kanalen.", "OptionAutomatic": "Automatisch", - "HeaderServices": "Services", + "HeaderServices": "Diensten", "LiveTvPluginRequired": "Een Live TV service provider Plug-in is vereist om door te gaan.", "LiveTvPluginRequiredHelp": "Installeer a.u b een van onze beschikbare Plug-ins, zoals Next PVR of ServerWmc.", "LabelCustomizeOptionsPerMediaType": "Aanpassen voor mediatype", @@ -512,11 +512,11 @@ "EditCollectionItemsHelp": "Toevoegen of verwijderen van alle films, series, albums, boeken of games die u wilt groeperen in deze collectie.", "HeaderAddTitles": "Titels toevoegen", "LabelEnableDlnaPlayTo": "DLNA Afspelen met inschakelen", - "LabelEnableDlnaPlayToHelp": "Emby can detect devices within your network and offer the ability to remote control them.", + "LabelEnableDlnaPlayToHelp": "Emby kan apparaten detecteren binnen uw netwerk en biedt de mogelijkheid om ze op afstand te controleren", "LabelEnableDlnaDebugLogging": "DLNA foutopsporings logboek inschakelen", "LabelEnableDlnaDebugLoggingHelp": "Dit zal grote logboekbestanden maken en mag alleen worden gebruikt als dat nodig is voor het oplossen van problemen.", "LabelEnableDlnaClientDiscoveryInterval": "Interval voor het zoeken naar clients (seconden)", - "LabelEnableDlnaClientDiscoveryIntervalHelp": "Determines the duration in seconds between SSDP searches performed by Emby.", + "LabelEnableDlnaClientDiscoveryIntervalHelp": "Bepalend voor de duur in seconden tussen SSDP zoekopdrachten uitgevoerd door Emby.", "HeaderCustomDlnaProfiles": "Aangepaste profielen", "HeaderSystemDlnaProfiles": "Systeem Profielen", "CustomDlnaProfilesHelp": "Maak een aangepast profiel om een \u200b\u200bnieuw apparaat aan te maken of overschrijf een systeemprofiel.", @@ -533,7 +533,7 @@ "LabelFriendlyServerName": "Aangepaste servernaam", "LabelFriendlyServerNameHelp": "Deze naam wordt gebruikt om deze server te identificeren. Indien leeg gelaten, zal de naam van de computer worden gebruikt.", "LabelPreferredDisplayLanguage": "Voorkeurs weergavetaal:", - "LabelPreferredDisplayLanguageHelp": "Translating Emby is an ongoing project and is not yet complete.", + "LabelPreferredDisplayLanguageHelp": "Vertaling van Emby is een voortdurend project en is nog niet afgerond.", "LabelReadHowYouCanContribute": "Lees meer over hoe u kunt bijdragen.", "HeaderNewCollection": "Nieuwe Collectie", "ButtonSubmit": "Uitvoeren", @@ -541,7 +541,7 @@ "LabelCustomCss": "Aangepaste css:", "LabelCustomCssHelp": "Uw eigen aangepaste css voor de web-interface toepassen.", "LabelLocalHttpServerPortNumber": "Lokale http poort nummer:", - "LabelLocalHttpServerPortNumberHelp": "The tcp port number that Emby's http server should bind to.", + "LabelLocalHttpServerPortNumberHelp": "Het tcp poort nummer waar Emby's http server aan moet verbinden.", "LabelPublicHttpPort": "Publieke http poort nummer:", "LabelPublicHttpPortHelp": "Het publieke poortnummer dat moet worden toegewezen aan de lokale http poort.", "LabelPublicHttpsPort": "Publieke https poort nummer:", @@ -549,12 +549,12 @@ "LabelEnableHttps": "Rapporteer https als extern adres", "LabelEnableHttpsHelp": "Indien ingeschakeld, zal de server een https url rapporteren aan de client als het externe adres. Dit kan clients die nog geen ondersteuning voor https hebben onbruikbaar maken.", "LabelHttpsPort": "Lokale https poort nummer:", - "LabelHttpsPortHelp": "The tcp port number that Emby's https server should bind to.", + "LabelHttpsPortHelp": "Het tcp poort nummer waar Emby's http server aan moet verbinden.", "LabelWebSocketPortNumber": "Web socket poortnummer:", "LabelEnableAutomaticPortMap": "Schakel automatisch poort vertalen in", "LabelEnableAutomaticPortMapHelp": "Probeer om de publieke poort automatisch te vertalen naar de lokale poort via UPnP. Dit werk niet op alle routers.", "LabelExternalDDNS": "Extern WAN Adres:", - "LabelExternalDDNSHelp": "If you have a dynamic DNS enter it here. Emby apps will use it when connecting remotely. Leave empty for automatic detection.", + "LabelExternalDDNSHelp": "Als u een dynamische DNS heeft moet dit hier worden ingevoerd. Emby apps zullen het gebruiken als externe verbinding. Laat leeg voor automatische detectie", "TabResume": "Hervatten", "TabWeather": "Weer", "TitleAppSettings": "App Instellingen", @@ -581,7 +581,7 @@ "LabelEpisodeNumber": "Aflevering nummer:", "LabelEndingEpisodeNumber": "Laatste aflevering nummer:", "LabelEndingEpisodeNumberHelp": "Alleen vereist voor bestanden met meerdere afleveringen", - "HeaderSupportTheTeam": "Support the Emby Team", + "HeaderSupportTheTeam": "Ondersteun het Emby Team", "LabelSupportAmount": "Bedrag (USD)", "HeaderSupportTheTeamHelp": "Door te doneren draagt u mee aan de verdere ontwikkeling van dit project. Een deel van alle donaties zal worden bijgedragen aan de andere gratis tools waarvan we afhankelijk zijn.", "ButtonEnterSupporterKey": "Voer supporter sleutel in", @@ -613,7 +613,7 @@ "OptionMove": "Verplaats", "LabelTransferMethodHelp": "Bestanden kopi\u00ebren of verplaatsen van de bewaakte map", "HeaderLatestNews": "Nieuws", - "HeaderHelpImproveProject": "Help Improve Emby", + "HeaderHelpImproveProject": "Help Emby te Verbeteren", "HeaderRunningTasks": "Actieve taken", "HeaderActiveDevices": "Actieve apparaten", "HeaderPendingInstallations": "In afwachting van installaties", @@ -624,8 +624,8 @@ "ButtonUpdateNow": "Nu bijwerken", "TabHosting": "Hosting", "PleaseUpdateManually": "Sluit de server a.u.b. af en werk handmatig bij.", - "NewServerVersionAvailable": "A new version of Emby Server is available!", - "ServerUpToDate": "Emby Server is up to date", + "NewServerVersionAvailable": "Er is een nieuwe versie van Emby Server beschikbaar!", + "ServerUpToDate": "Emby Server is up-to-date", "LabelComponentsUpdated": "De volgende onderdelen zijn ge\u00efnstalleerd of bijgewerkt:", "MessagePleaseRestartServerToFinishUpdating": "Herstart de server om de updates af te ronden en te activeren.", "LabelDownMixAudioScale": "Audio boost verbeteren wanneer wordt gemixt:", @@ -642,13 +642,13 @@ "LabelSupporterEmailAddress": "Het e-mailadres dat is gebruikt om de sleutel te kopen.", "ButtonRetrieveKey": "Ophalen Sleutel", "LabelSupporterKey": "Supporter Sleutel (plakken uit e-mail)", - "LabelSupporterKeyHelp": "Enter your supporter key to start enjoying additional benefits the community has developed for Emby.", + "LabelSupporterKeyHelp": "Voer uw supporter sleutel in om te genieten van de voordelen die de gemeenschap voor Emby heeft ontwikkeld.", "MessageInvalidKey": "Supporters sleutel ontbreekt of is ongeldig.", - "ErrorMessageInvalidKey": "In order for any premium content to be registered, you must also be an Emby Supporter. Please donate and support the continued development of the core product. Thank you.", + "ErrorMessageInvalidKey": "Om premium inhoud te registreren moet u ook Emby Supporter zijn. Doneer alstublieft en ondersteun daarmee de voortdurende ontwikkeling van het kernproduct. Bedankt.", "HeaderDisplaySettings": "Weergave-instellingen", "TabPlayTo": "Afspelen met", "LabelEnableDlnaServer": "DLNA Server inschakelen", - "LabelEnableDlnaServerHelp": "Allows UPnP devices on your network to browse and play Emby content.", + "LabelEnableDlnaServerHelp": "Sta UPnP apparaten op uw netwerk toe om door Emby inhoud te bladeren en af te spelen.", "LabelEnableBlastAliveMessages": "Zend alive berichten", "LabelEnableBlastAliveMessagesHelp": "Zet dit aan als de server niet betrouwbaar door andere UPnP-apparaten op uw netwerk wordt gedetecteerd.", "LabelBlastMessageInterval": "Alive bericht interval (seconden)", @@ -685,7 +685,7 @@ "NotificationOptionNewLibraryContentMultiple": "Nieuwe content toegevoegd (meerdere)", "NotificationOptionCameraImageUploaded": "Camera afbeelding ge\u00fcpload", "NotificationOptionUserLockedOut": "Gebruiker uitgesloten", - "HeaderSendNotificationHelp": "By default, notifications are delivered to your dashboard inbox. Browse the plugin catalog to install additional notification options.", + "HeaderSendNotificationHelp": "Meldingen worden geplaatst in de inbox op het dashboard. Blader door de Plug-in catalogus om aanvullende opties voor meldingen te installeren.", "NotificationOptionServerRestartRequired": "Server herstart nodig", "LabelNotificationEnabled": "Deze melding inschakelen", "LabelMonitorUsers": "Monitor activiteit van:", @@ -788,7 +788,7 @@ "LabelIconMaxHeight": "Pictogram max. hoogte:\n", "LabelIconMaxHeightHelp": "Max. resolutie van pictogrammen weergegeven via upnp:icon.", "LabelIdentificationFieldHelp": "Een niet-hoofdlettergevoelige subtekenreeks of regex expressie.", - "HeaderProfileServerSettingsHelp": "These values control how Emby Server will present itself to the device.", + "HeaderProfileServerSettingsHelp": "Deze waarden bepalen hoe Emby Server zich zal presenteren aan het apparaat.", "LabelMaxBitrate": "Max. bitrate:", "LabelMaxBitrateHelp": "Geef een max. bitrate in bandbreedte beperkte omgevingen, of als het apparaat zijn eigen limiet heeft.", "LabelMaxStreamingBitrate": "Maximale streaming bitrate:", @@ -829,7 +829,7 @@ "OptionEstimateContentLength": "Lengte schatten van de inhoud bij het transcoderen", "OptionReportByteRangeSeekingWhenTranscoding": "Rapporteer dat de server byte zoeken tijdens transcoderen ondersteunt", "OptionReportByteRangeSeekingWhenTranscodingHelp": "Dit is vereist voor bepaalde apparaten die zo goed op tijd zoeken.", - "HeaderSubtitleDownloadingHelp": "When Emby scans your video files it can search for missing subtitles, and download them using a subtitle provider such as OpenSubtitles.org.", + "HeaderSubtitleDownloadingHelp": "Wanneer Emby uw videobestanden scant kan het zoeken naar missende ondertitels, en download ze met behulp van een ondertitel provider zoals OpenSubtitlesorg", "HeaderDownloadSubtitlesFor": "Download ondertiteling voor:", "MessageNoChapterProviders": "Installeer een hoofdstuk provider Plug-in zoals ChapterDb om extra hoofdstuk opties in te schakelen.", "LabelSkipIfGraphicalSubsPresent": "Overslaan als de video al grafische ondertitels bevat", @@ -839,7 +839,7 @@ "HeaderDownloadChaptersFor": "Download hoofdstuk namen voor:", "LabelOpenSubtitlesUsername": "Gebruikersnaam Open Subtitles:", "LabelOpenSubtitlesPassword": "Wachtwoord Open Subtitles:", - "HeaderChapterDownloadingHelp": "When Emby scans your video files it can download friendly chapter names from the internet using chapter plugins such as ChapterDb.", + "HeaderChapterDownloadingHelp": "Wanneer Emby uw videobestanden scant kan het vriendelijke hoofdstuk namen downloaden van het internet met behulp van hoofdstuk plugins zoals ChapterDb", "LabelPlayDefaultAudioTrack": "Speel standaard audio spoor ongeacht taal", "LabelSubtitlePlaybackMode": "Ondertitelingsmode:", "LabelDownloadLanguages": "Download talen:", @@ -902,7 +902,7 @@ "OptionDefaultSort": "Standaard", "OptionCommunityMostWatchedSort": "Meest bekeken", "TabNextUp": "Volgend", - "HeaderBecomeProjectSupporter": "Become an Emby Supporter", + "HeaderBecomeProjectSupporter": "Wordt Emby Supporter", "TextEnjoyBonusFeatures": "Geniet van Bonus Features", "MessageNoMovieSuggestionsAvailable": "Er zijn momenteel geen film suggesties beschikbaar. Begin met het bekijken en waardeer uw films, kom daarna terug om uw aanbevelingen te bekijken.", "MessageNoCollectionsAvailable": "Collecties maken het u mogelijk om Films, Series, Albums, Boeken en Games te groeperen. Klik op de + knop om Collecties aan te maken.", @@ -921,7 +921,7 @@ "LabelChannelDownloadAgeHelp": "Gedownloade inhoud die ouder is zal worden verwijderd. Afspelen via internet streaming blijft mogelijk.", "ChannelSettingsFormHelp": "Installeer kanalen zoals Trailers en Vimeo in de Plug-in catalogus.", "ButtonOptions": "Opties", - "ViewTypePlaylists": "Playlists", + "ViewTypePlaylists": "Afspeellijsten", "ViewTypeMovies": "Films", "ViewTypeTvShows": "TV", "ViewTypeGames": "Games", @@ -969,9 +969,9 @@ "LabelProtocolInfo": "Protocol info:", "LabelProtocolInfoHelp": "De waarde die wordt gebruikt bij het reageren op GetProtocolInfo verzoeken van het apparaat.", "TabNfo": "Nfo", - "HeaderKodiMetadataHelp": "Emby includes native support for Nfo metadata files. To enable or disable Nfo metadata, use the Advanced tab to configure options for your media types.", + "HeaderKodiMetadataHelp": "Emby omvat native ondersteuning voor Nfo metadata bestanden. Om Nfo metadata in- of uit te schakelen, gebruikt u het tabblad Geavanceerd om opties te configureren voor uw mediatypen.", "LabelKodiMetadataUser": "Synchroniseer gekeken informatie toe aan NFO's voor (gebruiker):", - "LabelKodiMetadataUserHelp": "Enable this to keep watch data in sync between Emby Server and Nfo files.", + "LabelKodiMetadataUserHelp": "Schakel dit in om gemonitorde gegevens in sync te houden tussen Emby Server en Nfo bestanden.", "LabelKodiMetadataDateFormat": "Uitgave datum formaat:", "LabelKodiMetadataDateFormatHelp": "Alle datums in NFO's zullen gelezen en geschreven worden met dit formaat.", "LabelKodiMetadataSaveImagePaths": "Bewaar afbeeldingspaden in NFO-bestanden", @@ -989,7 +989,7 @@ "TabLogs": "Logboeken", "HeaderServerLogFiles": "Server logboek bestanden:", "TabBranding": "Huisstijl", - "HeaderBrandingHelp": "Customize the appearance of Emby to fit the needs of your group or organization.", + "HeaderBrandingHelp": "Pas het uiterlijk van Emby aan, aan de behoeften van uw groep of organisatie.", "LabelLoginDisclaimer": "Aanmeld vrijwaring:", "LabelLoginDisclaimerHelp": "Dit wordt onderaan de login pagina weergegeven.", "LabelAutomaticallyDonate": "Doneer dit bedrag automatisch elke maand", @@ -1005,7 +1005,7 @@ "HeaderLatestMusic": "Nieuwste muziek", "HeaderBranding": "Huisstijl", "HeaderApiKeys": "Api Sleutels", - "HeaderApiKeysHelp": "External applications are required to have an Api key in order to communicate with Emby Server. Keys are issued by logging in with an Emby account, or by manually granting the application a key.", + "HeaderApiKeysHelp": "Externe applicaties zijn verplicht om een \u200b\u200bAPI sleutel te hebben om te communiceren met Emby Server. Sleutels worden uitgegeven door in te loggen met een Emby account, of door het handmatig verlenen van een sleutel voor de toepassing.", "HeaderApiKey": "Api Sleutel", "HeaderApp": "Applicatie", "HeaderDevice": "Apparaat", @@ -1015,7 +1015,7 @@ "HeaderNewApiKey": "Nieuwe Api sleutel", "LabelAppName": "Applicatie Naam", "LabelAppNameExample": "Voorbeeld: Sickbeard, NzbDrone", - "HeaderNewApiKeyHelp": "Grant an application permission to communicate with Emby Server.", + "HeaderNewApiKeyHelp": "Geef een applicatie toestemming om te communiceren met Emby Server.", "HeaderHttpHeaders": "Http Headers", "HeaderIdentificationHeader": "Identificatie Header", "LabelValue": "Waarde:", @@ -1119,7 +1119,7 @@ "UserDeletedWithName": "Gebruiker {0} is verwijderd", "MessageServerConfigurationUpdated": "Server configuratie is bijgewerkt", "MessageNamedServerConfigurationUpdatedWithValue": "Sectie {0} van de server configuratie is bijgewerkt", - "MessageApplicationUpdated": "Emby Server has been updated", + "MessageApplicationUpdated": "Emby Server is bijgewerkt", "AuthenticationSucceededWithUserName": "{0} is succesvol geverifieerd", "FailedLoginAttemptWithUserName": "Mislukte aanmeld poging van {0}", "UserDownloadingItemWithValues": "{0} download {1}", @@ -1140,14 +1140,14 @@ "ViewTypeLiveTvRecordingGroups": "Opnamen", "ViewTypeLiveTvChannels": "Kanalen", "LabelEasyPinCode": "Eenvoudige pincode:", - "EasyPasswordHelp": "Your easy pin code is used for offline access with supported Emby apps, and can also be used for easy in-network sign in.", + "EasyPasswordHelp": "Uw gemakkelijk pincode wordt gebruikt voor offline toegang met ondersteunde Emby apps, en kan ook worden gebruikt voor eenvoudige in-netwerk aanmelden.", "LabelInNetworkSignInWithEasyPassword": "Schakel eenvoudige lokale aanmelding in met mijn easy pin code", - "LabelInNetworkSignInWithEasyPasswordHelp": "If enabled, you'll be able to use your easy pin code to sign in to Emby apps from inside your home network. Your regular password will only be needed away from home. If the pin code is left blank, you won't need a password within your home network.", + "LabelInNetworkSignInWithEasyPasswordHelp": "Indien ingeschakeld, zult u in staat zijn om uw gemakkelijke pincode gebruiken om u aan te melden bij Emby apps van binnen uw thuisnetwerk. Uw reguliere wachtwoord is nodig buiten uw thuisnetwerk. Als U de pincode leeg laat, heeft U geen wachtwoord nodig in uw thuisnetwerk.", "HeaderPassword": "Wachtwoord", "HeaderLocalAccess": "Lokale toegang", "HeaderViewOrder": "Weergave volgorde", "ButtonResetEasyPassword": "Reset eenvoudige pincode", - "LabelSelectUserViewOrder": "Choose the order your views will be displayed in within Emby apps", + "LabelSelectUserViewOrder": "Kies de volgorde van uw weergaven zoals deze worden weergegeven binnen Emby apps", "LabelMetadataRefreshMode": "Metadata vernieuw mode:", "LabelImageRefreshMode": "Afbeelding vernieuw mode:", "OptionDownloadMissingImages": "Ontbrekende afbeeldingen downloaden", @@ -1164,7 +1164,7 @@ "LabelIfYouWishToContinueWithDeletion": "Geef om door te gaan het resultaat in:", "ButtonIdentify": "Identificeer", "LabelAlbumArtist": "Album artiest:", - "LabelAlbumArtists": "Album artists:", + "LabelAlbumArtists": "Album artiesten:", "LabelAlbum": "Album:", "LabelCommunityRating": "Beoordeling gemeenschap:", "LabelVoteCount": "Aantal stemmen:", @@ -1249,10 +1249,10 @@ "OptionSaveMetadataAsHidden": "Metagegevens en afbeeldingen opslaan als verborgen bestanden", "LabelExtractChaptersDuringLibraryScan": "Hoofdstuk afbeeldingen uitpakken tijdens het scannen van de bibliotheek", "LabelExtractChaptersDuringLibraryScanHelp": "Wanneer ingeschakeld worden hoofdstuk afbeeldingen uitgepakt wanneer video's worden ge\u00efmporteerd tijdens het scannen van de bibliotheek. Wanneer uitgeschakeld worden de hoofdstuk afbeeldingen uitgepakt tijdens de geplande taak \"Hoofdstukken uitpakken\", waardoor de standaard bibliotheek scan sneller voltooid is.", - "LabelConnectGuestUserName": "Their Emby username or email address:", - "LabelConnectUserName": "Emby username\/email:", - "LabelConnectUserNameHelp": "Connect this user to an Emby account to enable easy sign-in access from any Emby app without having to know the server ip address.", - "ButtonLearnMoreAboutEmbyConnect": "Learn more about Emby Connect", + "LabelConnectGuestUserName": "Hun Emby gebruikersnaam of email adres:", + "LabelConnectUserName": "Emby gebruikersnaam\/email:", + "LabelConnectUserNameHelp": "Verbind deze gebruiker aan een Emby account om eenvoudig aanmelden vanaf elke Emby app toe te staan zonder dat u het IP-adres hoeft te weten.", + "ButtonLearnMoreAboutEmbyConnect": "Leer meer over Emby Connect", "LabelExternalPlayers": "Externe spelers:", "LabelExternalPlayersHelp": "Toon knoppen om inhoud in externe spelers of te spelen. Dit is alleen mogelijk op apparaten die 'url schemes' ondersteunen, meest Android en iOS. Met externe spelers is er over het algemeen geen ondersteuning voor afstandsbediening of hervatten.", "HeaderSubtitleProfile": "Ondertitelingsprofiel", @@ -1300,7 +1300,7 @@ "TitleDevices": "Apparaten", "TabCameraUpload": "Camera upload", "TabDevices": "Apparaten", - "HeaderCameraUploadHelp": "Automatically upload photos and videos taken from your mobile devices into Emby.", + "HeaderCameraUploadHelp": "Foto's en video's genomen met uw mobiele apparaten automatisch uploaden naar Emby .", "MessageNoDevicesSupportCameraUpload": "U hebt op dit moment geen apparaten die camera upload ondersteunen.", "LabelCameraUploadPath": "Camera upload pad:", "LabelCameraUploadPathHelp": "Geef een eigen upload pad op, indien gewenst. Deze map moet ook aan de bibliotheek instellingen toegevoegd worden. Als er niets opgegeven is wordt de standaard map gebruikt.", @@ -1309,10 +1309,10 @@ "LabelCustomDeviceDisplayName": "Weergave naam:", "LabelCustomDeviceDisplayNameHelp": "Geef een eigen weergave naam op of laat deze leeg om de naam te gebruiken die het apparaat opgeeft.", "HeaderInviteUser": "Nodig gebruiker uit", - "LabelConnectGuestUserNameHelp": "This is the username that your friend uses to sign in to the Emby website, or their email address.", - "HeaderInviteUserHelp": "Sharing your media with friends is easier than ever before with Emby Connect.", + "LabelConnectGuestUserNameHelp": "Dit is de gebruikersnaam die je vriend gebruikt om zich aan te melden bij de Emby website, of hun e-mailadres", + "HeaderInviteUserHelp": "Het delen van je media met vrienden is eenvoudiger dan ooit met Emby Connect.", "ButtonSendInvitation": "Stuur uitnodiging", - "HeaderSignInWithConnect": "Sign in with Emby Connect", + "HeaderSignInWithConnect": "Meld je aan met Emby Connect", "HeaderGuests": "Gasten", "HeaderLocalUsers": "Lokale gebruikers", "HeaderPendingInvitations": "Uitstaande uitnodigingen", @@ -1327,8 +1327,8 @@ "OptionEveryday": "Elke dag", "OptionWeekdays": "Week dagen", "OptionWeekends": "Weekend", - "MessageProfileInfoSynced": "User profile information synced with Emby Connect.", - "HeaderOptionalLinkEmbyAccount": "Optional: Link your Emby account", + "MessageProfileInfoSynced": "Gebruikersprofiel informatie gesynchroniseerd met Emby Connect.", + "HeaderOptionalLinkEmbyAccount": "Optioneel: Koppel uw Emby account", "ButtonTrailerReel": "Trailer reel", "HeaderTrailerReel": "Trailer reel", "OptionPlayUnwatchedTrailersOnly": "Speel alleen ongeziene trailers", @@ -1378,11 +1378,13 @@ "LabelTagFilterAllowModeHelp": "Als toegestane tags worden gebruikt in een diep geneste mappenstructuur, zal getagde inhoud vereisen dat de bovenliggende mappen ook getagd zijn.", "HeaderThisUserIsCurrentlyDisabled": "Deze gebruiker is momenteel uitgesloten", "MessageReenableUser": "Zie hieronder hoe opnieuw in te schakelen", - "LabelEnableInternetMetadataForTvPrograms": "Download internet metadata for:", + "LabelEnableInternetMetadataForTvPrograms": "Download internet metadata voor:", "OptionTVMovies": "TV Movies", - "HeaderUpcomingMovies": "Upcoming Movies", - "HeaderUpcomingPrograms": "Upcoming Programs", + "HeaderUpcomingMovies": "Aankomende Films", + "HeaderUpcomingPrograms": "Aankomende Programma's", "ButtonMoreItems": "Meer...", - "LabelShowLibraryTileNames": "Show library tile names", - "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page" + "LabelShowLibraryTileNames": "Toon bibliotheek tegel namen", + "LabelShowLibraryTileNamesHelp": "Bepaalt of labels onder de bibliotheek tegels zullen worden weergegeven op de startpagina", + "OptionEnableTranscodingThrottle": "Enable throttling", + "OptionEnableTranscodingThrottleHelp": "Throttling will automatically adjust transcoding speed in order to minimize server cpu utilization during playback." } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/pl.json b/MediaBrowser.Server.Implementations/Localization/Server/pl.json index 9d83c099c7..ef441fccd5 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/pl.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/pl.json @@ -1384,5 +1384,7 @@ "HeaderUpcomingPrograms": "Upcoming Programs", "ButtonMoreItems": "More...", "LabelShowLibraryTileNames": "Show library tile names", - "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page" + "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page", + "OptionEnableTranscodingThrottle": "Enable throttling", + "OptionEnableTranscodingThrottleHelp": "Throttling will automatically adjust transcoding speed in order to minimize server cpu utilization during playback." } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/pt_BR.json b/MediaBrowser.Server.Implementations/Localization/Server/pt_BR.json index 8308cd6696..20d7e32fa5 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/pt_BR.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/pt_BR.json @@ -701,8 +701,8 @@ "OptionAllUsers": "Todos os usu\u00e1rios", "OptionAdminUsers": "Administradores", "OptionCustomUsers": "Personalizado", - "ButtonArrowUp": "Para cima", - "ButtonArrowDown": "Para baixo", + "ButtonArrowUp": "Subir", + "ButtonArrowDown": "Descer", "ButtonArrowLeft": "Esquerda", "ButtonArrowRight": "Direita", "ButtonBack": "Voltar", @@ -1298,16 +1298,16 @@ "LabelDateAddedBehaviorHelp": "Se um valor de metadata estiver presente, ele sempre ser\u00e1 utilizado antes destas op\u00e7\u00f5es.", "LabelNumberTrailerToPlay": "N\u00famero de trailers a serem apresentados:", "TitleDevices": "Dispositivos", - "TabCameraUpload": "Carga atrav\u00e9s de c\u00e2mera", + "TabCameraUpload": "Upload da C\u00e2mera", "TabDevices": "Dispositivos", "HeaderCameraUploadHelp": "Faz o upload autom\u00e1tico de fotos e v\u00eddeos tiradas por seus dispositivos m\u00f3veis para o Emby.", - "MessageNoDevicesSupportCameraUpload": "Atualmente voc\u00ea n\u00e3o tem nenhum dispositivo que suporte carga atrav\u00e9s da c\u00e2mera.", - "LabelCameraUploadPath": "Caminho para carga atrav\u00e9s da c\u00e2mera:", + "MessageNoDevicesSupportCameraUpload": "Atualmente voc\u00ea n\u00e3o tem nenhum dispositivo que suporte upload da c\u00e2mera.", + "LabelCameraUploadPath": "Caminho para upload da c\u00e2mera:", "LabelCameraUploadPathHelp": "Selecione um caminho personalizado para upload, se desejar. Se n\u00e3o definir, a pasta padr\u00e3o ser\u00e1 usada. Se usar um caminho personalizado, ser\u00e1 necess\u00e1rio adicionar na \u00e1rea de ajustes da biblioteca.", "LabelCreateCameraUploadSubfolder": "Criar uma subpasta para cada dispositivo", "LabelCreateCameraUploadSubfolderHelp": "Pastas espec\u00edficas podem ser atribu\u00eddas a um dispositivo clicando-as na p\u00e1gina de Dispositivos.", "LabelCustomDeviceDisplayName": "Nome para exibi\u00e7\u00e3o:", - "LabelCustomDeviceDisplayNameHelp": "Forne\u00e7a um nome para exibi\u00e7\u00e3o ou deixe vazio para usar o nome informado pelo dispositivo.", + "LabelCustomDeviceDisplayNameHelp": "Forne\u00e7a um nome para exibi\u00e7\u00e3o ou deixe em branco para usar o nome informado pelo dispositivo.", "HeaderInviteUser": "Convidar usu\u00e1rio", "LabelConnectGuestUserNameHelp": "Este \u00e9 o nome de usu\u00e1rio que seu amigo usa para entrar no website do Emby, ou seu endere\u00e7o de email.", "HeaderInviteUserHelp": "Compartilhar sua m\u00eddia com amigos nunca foi t\u00e3o f\u00e1cil com o Emby Connect.", @@ -1384,5 +1384,7 @@ "HeaderUpcomingPrograms": "Programas Por Estrear", "ButtonMoreItems": "Mais...", "LabelShowLibraryTileNames": "Mostrar os nomes das tiles da biblioteca", - "LabelShowLibraryTileNamesHelp": "Determina se os t\u00edtulos ser\u00e3o exibidos embaixo das tiles da biblioteca na p\u00e1gina in\u00edcio" + "LabelShowLibraryTileNamesHelp": "Determina se os t\u00edtulos ser\u00e3o exibidos embaixo das tiles da biblioteca na p\u00e1gina in\u00edcio", + "OptionEnableTranscodingThrottle": "Ativar controlador de fluxo", + "OptionEnableTranscodingThrottleHelp": "O controlador de fluxo ajustar\u00e1 automaticamente a velocidade de transcodifica\u00e7\u00e3o para minimizar o uso da cpu no servidor durante a reprodu\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 index b4bcf09ac9..dec7d35d24 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/pt_PT.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/pt_PT.json @@ -7,7 +7,7 @@ "LabelApiDocumentation": "Api Documentation", "LabelDeveloperResources": "Developer Resources", "LabelBrowseLibrary": "Navegar pela Biblioteca", - "LabelConfigureServer": "Configure Emby", + "LabelConfigureServer": "Configurar o Emby", "LabelOpenLibraryViewer": "Abrir Visualizador da Biblioteca", "LabelRestartServer": "Reiniciar Servidor", "LabelShowLogWindow": "Mostrar Janela de Log", @@ -15,16 +15,16 @@ "LabelFinish": "Terminar", "LabelNext": "Seguinte", "LabelYoureDone": "Concluiu!", - "WelcomeToProject": "Welcome to Emby!", + "WelcomeToProject": "Bem-vindo ao Emby!", "ThisWizardWillGuideYou": "Este assistente ir\u00e1 ajud\u00e1-lo durante o processo de configura\u00e7\u00e3o. Para come\u00e7ar, selecione o idioma.", "TellUsAboutYourself": "Fale-nos sobre si", "ButtonQuickStartGuide": "Quick start guide", "LabelYourFirstName": "O seu primeiro nome:", "MoreUsersCanBeAddedLater": "\u00c9 poss\u00edvel adicionar utilizadores mais tarde no Painel Principal", - "UserProfilesIntro": "Emby includes built-in support for user profiles, enabling each user to have their own display settings, playstate and parental controls.", + "UserProfilesIntro": "O Emby inclui suporte nativo de perfis de utilizadores, permitindo que cada utilizador tenha as suas configura\u00e7\u00f5es de visualiza\u00e7\u00e3o, estado da reprodu\u00e7\u00e3o e controlos parentais.", "LabelWindowsService": "Servi\u00e7o do Windows", "AWindowsServiceHasBeenInstalled": "Foi instalado um Servi\u00e7o do Windows.", - "WindowsServiceIntro1": "Emby 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.", + "WindowsServiceIntro1": "O Servidor Emby \u00e9 normalmente executado como uma aplica\u00e7\u00e3o de ambiente de trabalho com um \u00edcone na barra de tarefas, mas se o preferir executar como um servi\u00e7o em segundo plano, pode ser iniciado no painel de controlo de servi\u00e7os do windows.", "WindowsServiceIntro2": "Por favor tome aten\u00e7\u00e3o que se estiver a usar o servi\u00e7o, este n\u00e3o pode estar a correr ao mesmo tempo que o \u00edcone na bandeja. Por isso, ter\u00e1 de sair da aplca\u00e7\u00e3o da bandeja para poder correr o servi\u00e7o. Note, ainda, que o servi\u00e7o necessita de privil\u00e9gios administrativos via Painel de Controlo. De momento, n\u00e3o \u00e9 poss\u00edvel utilizar a fun\u00e7\u00e3o de auto-actualiza\u00e7\u00e3o ao mesmo tempo que est\u00e1 em utiliza\u00e7\u00e3o o servi\u00e7o, por isso, novas vers\u00f5es necessitam de interac\u00e7\u00e3o manual.", "WizardCompleted": "That's all we need for now. Emby has begun collecting information about your media library. Check out some of our apps, and then click Finish<\/b> to view the Server Dashboard<\/b>.", "LabelConfigureSettings": "Configura\u00e7\u00f5es", @@ -34,7 +34,7 @@ "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, although this is configurable in the scheduled tasks area. It is not recommended to run this task during peak usage hours.", "LabelEnableAutomaticPortMapping": "Activar mapeamento autom\u00e1tico de portas", "LabelEnableAutomaticPortMappingHelp": "UPnP permite configurar automaticamente o router, para um acesso remoto mais facilitado. Pode n\u00e3o suportar todos os modelos de routers.", - "HeaderTermsOfService": "Emby Terms of Service", + "HeaderTermsOfService": "Termos de Servi\u00e7o do Emby", "MessagePleaseAcceptTermsOfService": "Please accept the terms of service and privacy policy before continuing.", "OptionIAcceptTermsOfService": "I accept the terms of service", "ButtonPrivacyPolicy": "Privacy policy", @@ -613,7 +613,7 @@ "OptionMove": "Mover", "LabelTransferMethodHelp": "Copiar ou mover ficheiros da pasta observada", "HeaderLatestNews": "\u00daltimas Not\u00edcias", - "HeaderHelpImproveProject": "Help Improve Emby", + "HeaderHelpImproveProject": "Ajude a Melhorar o Emby", "HeaderRunningTasks": "Tarefas em Execu\u00e7\u00e3o", "HeaderActiveDevices": "Dispositivos Ativos", "HeaderPendingInstallations": "Instala\u00e7\u00f5es Pendentes", @@ -624,8 +624,8 @@ "ButtonUpdateNow": "Atualizar Agora", "TabHosting": "Hosting", "PleaseUpdateManually": "Por favor encerre o servidor e atualize manualmente.", - "NewServerVersionAvailable": "A new version of Emby Server is available!", - "ServerUpToDate": "Emby Server is up to date", + "NewServerVersionAvailable": "Uma nova vers\u00e3o do Servidor Emby est\u00e1 dispon\u00edvel!", + "ServerUpToDate": "O Servidor Emby est\u00e1 atualizado", "LabelComponentsUpdated": "Os componentes seguintes foram instalados ou atualizados:", "MessagePleaseRestartServerToFinishUpdating": "Por favor reinicie o servidor para terminar a aplica\u00e7\u00e3o das atualiza\u00e7\u00f5es.", "LabelDownMixAudioScale": "Escala do aumento de \u00e1udio ao fazer downmix:", @@ -1252,7 +1252,7 @@ "LabelConnectGuestUserName": "Their Emby username or email address:", "LabelConnectUserName": "Emby username\/email:", "LabelConnectUserNameHelp": "Connect this user to an Emby account to enable easy sign-in access from any Emby app without having to know the server ip address.", - "ButtonLearnMoreAboutEmbyConnect": "Learn more about Emby Connect", + "ButtonLearnMoreAboutEmbyConnect": "Saiba mais sobre o Emby Connect", "LabelExternalPlayers": "External players:", "LabelExternalPlayersHelp": "Display buttons to play content in external players. This is only available on devices that support url schemes, generally Android and iOS. With external players there is generally no support for remote control or resuming.", "HeaderSubtitleProfile": "Subtitle Profile", @@ -1384,5 +1384,7 @@ "HeaderUpcomingPrograms": "Upcoming Programs", "ButtonMoreItems": "More...", "LabelShowLibraryTileNames": "Show library tile names", - "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page" + "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page", + "OptionEnableTranscodingThrottle": "Enable throttling", + "OptionEnableTranscodingThrottleHelp": "Throttling will automatically adjust transcoding speed in order to minimize server cpu utilization during playback." } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/ru.json b/MediaBrowser.Server.Implementations/Localization/Server/ru.json index 1b8bff187a..29c1b9d12f 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/ru.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/ru.json @@ -18,7 +18,7 @@ "WelcomeToProject": "Emby \u043f\u0440\u0438\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0432\u0430\u0441!", "ThisWizardWillGuideYou": "\u042d\u0442\u043e\u0442 \u043f\u043e\u043c\u043e\u0449\u043d\u0438\u043a \u043f\u0440\u043e\u0432\u0435\u0434\u0451\u0442 \u0432\u0430\u0441 \u0447\u0435\u0440\u0435\u0437 \u0432\u0441\u0435 \u0444\u0430\u0437\u044b \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438. \u0427\u0442\u043e\u0431\u044b \u043d\u0430\u0447\u0430\u0442\u044c, \u0432\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0441\u0432\u043e\u0439 \u043f\u0440\u0435\u0434\u043f\u043e\u0447\u0438\u0442\u0430\u0435\u043c\u044b\u0439 \u044f\u0437\u044b\u043a.", "TellUsAboutYourself": "\u0420\u0430\u0441\u0441\u043a\u0430\u0436\u0438\u0442\u0435 \u043e \u0441\u0435\u0431\u0435", - "ButtonQuickStartGuide": "\u041a \u043a\u0440\u0430\u0442\u043a\u043e\u043c\u0443 \u0440\u0443\u043a\u043e\u0432\u043e\u0434\u0441\u0442\u0432\u0443 \u043f\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0443", + "ButtonQuickStartGuide": "\u041a\u043e \u043a\u0440\u0430\u0442\u043a\u043e\u043c\u0443 \u0440\u0443\u043a\u043e\u0432\u043e\u0434\u0441\u0442\u0432\u0443 \u043f\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0443", "LabelYourFirstName": "\u0412\u0430\u0448\u0435 \u0438\u043c\u044f:", "MoreUsersCanBeAddedLater": "\u041f\u043e\u0442\u043e\u043c \u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0435\u0449\u0451 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u0447\u0435\u0440\u0435\u0437 \u00ab\u0418\u043d\u0444\u043e\u043f\u0430\u043d\u0435\u043b\u044c\u00bb.", "UserProfilesIntro": "\u0412 Emby \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u0430 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0430 \u0434\u043b\u044f \u043f\u0440\u043e\u0444\u0438\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\u0432\u043e\u0438 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \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 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435\u043c.", @@ -160,8 +160,8 @@ "ChannelAccessHelp": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043a\u0430\u043d\u0430\u043b\u044b \u0434\u043b\u044f \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043e\u0431\u0449\u0435\u0433\u043e \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u044d\u0442\u043e\u043c\u0443 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e. \u0410\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440\u044b \u0431\u0443\u0434\u0443\u0442 \u0432 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0438 \u043f\u0440\u0430\u0432\u0438\u0442\u044c \u0432\u0441\u0435 \u043a\u0430\u043d\u0430\u043b\u044b \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u00ab\u0414\u0438\u0441\u043f\u0435\u0442\u0447\u0435\u0440\u0430 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0445\u00bb.", "ButtonDeleteImage": "\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0440\u0438\u0441\u0443\u043d\u043e\u043a", "LabelSelectUsers": "\u0412\u044b\u0431\u043e\u0440 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439:", - "ButtonUpload": "\u041e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c", - "HeaderUploadNewImage": "\u041e\u0442\u043f\u0440\u0430\u0432\u043a\u0430 \u043d\u043e\u0432\u043e\u0433\u043e \u0440\u0438\u0441\u0443\u043d\u043a\u0430", + "ButtonUpload": "\u0412\u044b\u043b\u043e\u0436\u0438\u0442\u044c", + "HeaderUploadNewImage": "\u0412\u044b\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u043d\u0438\u0435 \u043d\u043e\u0432\u043e\u0433\u043e \u0440\u0438\u0441\u0443\u043d\u043a\u0430", "LabelDropImageHere": "\u041f\u0435\u0440\u0435\u0442\u0430\u0449\u0438\u0442\u0435 \u0440\u0438\u0441\u0443\u043d\u043e\u043a \u0441\u044e\u0434\u0430", "ImageUploadAspectRatioHelp": "\u0420\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u043c\u043e\u0435 \u0441\u043e\u043e\u0442\u043d\u043e\u0448\u0435\u043d\u0438\u0435 \u0441\u0442\u043e\u0440\u043e\u043d - 1:1. \u0414\u043e\u043f\u0443\u0441\u0442\u0438\u043c\u044b \u0442\u043e\u043b\u044c\u043a\u043e JPG\/PNG.", "MessageNothingHere": "\u0417\u0434\u0435\u0441\u044c \u043d\u0435\u0442 \u043d\u0438\u0447\u0435\u0433\u043e.", @@ -226,7 +226,7 @@ "OptionCriticRating": "\u041e\u0446\u0435\u043d\u043a\u0430 \u043a\u0440\u0438\u0442\u0438\u043a\u043e\u0432", "OptionVideoBitrate": "\u041f\u043e\u0442\u043e\u043a. \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u044c \u0432\u0438\u0434\u0435\u043e", "OptionResumable": "\u0412\u043e\u0437\u043e\u0431\u043d\u043e\u0432\u0438\u043c\u044b\u0435", - "ScheduledTasksHelp": "\u0429\u0451\u043b\u043a\u043d\u0438\u0442\u0435 \u043f\u043e \u0437\u0430\u0434\u0430\u0447\u0435, \u0447\u0442\u043e\u0431\u044b \u043f\u0435\u0440\u0435\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0435\u0451 \u0440\u0430\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0435.", + "ScheduledTasksHelp": "\u0429\u0451\u043b\u043a\u043d\u0438\u0442\u0435 \u043f\u043e \u0437\u0430\u0434\u0430\u0447\u0435, \u0447\u0442\u043e\u0431\u044b \u0441\u043a\u043e\u0440\u0440\u0435\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0435\u0451 \u0440\u0430\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0435.", "ScheduledTasksTitle": "\u041f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0449\u0438\u043a", "TabMyPlugins": "\u041c\u043e\u0438 \u043f\u043b\u0430\u0433\u0438\u043d\u044b", "TabCatalog": "\u041a\u0430\u0442\u0430\u043b\u043e\u0433", @@ -683,7 +683,7 @@ "NotificationOptionInstallationFailed": "\u0421\u0431\u043e\u0439 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438", "NotificationOptionNewLibraryContent": "\u041d\u043e\u0432\u043e\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e", "NotificationOptionNewLibraryContentMultiple": "\u041d\u043e\u0432\u043e\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e (\u043c\u043d\u043e\u0433\u043e\u043a\u0440\u0430\u0442\u043d\u043e)", - "NotificationOptionCameraImageUploaded": "\u0424\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u044f \u0441 \u043a\u0430\u043c\u0435\u0440\u044b \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0430", + "NotificationOptionCameraImageUploaded": "\u0424\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u044f \u0441 \u043a\u0430\u043c\u0435\u0440\u044b \u0432\u044b\u043b\u043e\u0436\u0435\u043d\u0430", "NotificationOptionUserLockedOut": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0437\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u043d", "HeaderSendNotificationHelp": "\u041f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e, \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f \u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u0432\u043e \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0435 \u0438\u043d\u0444\u043e\u043f\u0430\u043d\u0435\u043b\u0438. \u041f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u0442\u0435 \u043a\u0430\u0442\u0430\u043b\u043e\u0433 \u043f\u043b\u0430\u0433\u0438\u043d\u043e\u0432, \u0447\u0442\u043e\u0431\u044b \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438.", "NotificationOptionServerRestartRequired": "\u0422\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u043a \u0441\u0435\u0440\u0432\u0435\u0440\u0430", @@ -829,7 +829,7 @@ "OptionEstimateContentLength": "\u0420\u0430\u0441\u0441\u0447\u0438\u0442\u044b\u0432\u0430\u0442\u044c \u0434\u043b\u0438\u043d\u0443 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0433\u043e \u043f\u0440\u0438 \u043f\u0435\u0440\u0435\u043a\u043e\u0434\u0438\u0440\u043e\u0432\u043a\u0435", "OptionReportByteRangeSeekingWhenTranscoding": "\u0412\u044b\u0434\u0430\u0432\u0430\u0442\u044c, \u0447\u0442\u043e \u0441\u0435\u0440\u0432\u0435\u0440 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u043f\u043e\u0431\u0430\u0439\u0442\u043e\u0432\u0443\u044e \u043f\u0435\u0440\u0435\u043c\u043e\u0442\u043a\u0443 \u043f\u0440\u0438 \u043f\u0435\u0440\u0435\u043a\u043e\u0434\u0438\u0440\u043e\u0432\u043a\u0435", "OptionReportByteRangeSeekingWhenTranscodingHelp": "\u042d\u0442\u043e \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0434\u043b\u044f \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0434\u0435\u043b\u0430\u044e\u0442 \u043f\u043e\u0432\u0440\u0435\u043c\u0451\u043d\u043d\u0443\u044e \u043f\u0435\u0440\u0435\u043c\u043e\u0442\u043a\u0443 \u043d\u0435\u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0443\u0434\u043e\u0432\u043b\u0435\u0442\u0432\u043e\u0440\u0438\u0442\u0435\u043b\u044c\u043d\u043e.", - "HeaderSubtitleDownloadingHelp": "\u041f\u0440\u0438 \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438 \u0432\u0438\u0434\u0435\u043e\u0444\u0430\u0439\u043b\u043e\u0432 \u0432 Emby \u0438\u043c\u0435\u044e\u0442\u0441\u044f \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u043f\u043e\u0438\u0441\u043a \u043d\u0435\u0434\u043e\u0441\u0442\u0430\u044e\u0449\u0438\u0445 \u0441\u0443\u0431\u0442\u0438\u0442\u0440\u043e\u0432 \u0438 \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0438\u0445 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u0430 \u0441\u0443\u0431\u0442\u0438\u0442\u0440\u043e\u0432, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, OpenSubtitles.org.", + "HeaderSubtitleDownloadingHelp": "\u041f\u0440\u0438 \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438 \u0432\u0438\u0434\u0435\u043e\u0444\u0430\u0439\u043b\u043e\u0432 \u0432 Emby, \u0438\u043c\u0435\u044e\u0442\u0441\u044f \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u043f\u043e\u0438\u0441\u043a \u043d\u0435\u0434\u043e\u0441\u0442\u0430\u044e\u0449\u0438\u0445 \u0441\u0443\u0431\u0442\u0438\u0442\u0440\u043e\u0432 \u0438 \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0438\u0445 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u0430 \u0441\u0443\u0431\u0442\u0438\u0442\u0440\u043e\u0432, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, OpenSubtitles.org.", "HeaderDownloadSubtitlesFor": "\u0417\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0441\u0443\u0431\u0442\u0438\u0442\u0440\u043e\u0432 \u0434\u043b\u044f:", "MessageNoChapterProviders": "\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0435 \u043f\u043b\u0430\u0433\u0438\u043d-\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a \u0441\u0446\u0435\u043d (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440: ChapterDb) \u0434\u043b\u044f \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0435\u0439 \u0434\u043b\u044f \u0441\u0446\u0435\u043d.", "LabelSkipIfGraphicalSubsPresent": "\u041e\u043f\u0443\u0441\u0442\u0438\u0442\u044c, \u0435\u0441\u043b\u0438 \u0432\u0438\u0434\u0435\u043e \u0443\u0436\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0433\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u0441\u0443\u0431\u0442\u0438\u0442\u0440\u044b", @@ -839,7 +839,7 @@ "HeaderDownloadChaptersFor": "\u0417\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0439 \u0441\u0446\u0435\u043d \u0434\u043b\u044f:", "LabelOpenSubtitlesUsername": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f Open Subtitles:", "LabelOpenSubtitlesPassword": "\u041f\u0430\u0440\u043e\u043b\u044c Open Subtitles:", - "HeaderChapterDownloadingHelp": "\u041f\u0440\u0438 \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438 \u0432\u0438\u0434\u0435\u043e\u0444\u0430\u0439\u043b\u043e\u0432 \u0432 Emby \u0438\u043c\u0435\u044e\u0442\u0441\u044f \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u043f\u043e\u043d\u044f\u0442\u043d\u044b\u0445 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0439 \u0441\u0446\u0435\u043d \u0438\u0437 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u043f\u043b\u0430\u0433\u0438\u043d\u044b \u0441\u0446\u0435\u043d, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, ChapterDb.", + "HeaderChapterDownloadingHelp": "\u041f\u0440\u0438 \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438 \u0432\u0438\u0434\u0435\u043e\u0444\u0430\u0439\u043b\u043e\u0432 \u0432 Emby, \u0438\u043c\u0435\u044e\u0442\u0441\u044f \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u043f\u043e\u043d\u044f\u0442\u043d\u044b\u0445 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0439 \u0441\u0446\u0435\u043d \u0438\u0437 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u043f\u043b\u0430\u0433\u0438\u043d\u044b \u0441\u0446\u0435\u043d, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, ChapterDb.", "LabelPlayDefaultAudioTrack": "\u0412\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u044c \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u0443\u044e \u0430\u0443\u0434\u0438\u043e\u0434\u043e\u0440\u043e\u0436\u043a\u0443 \u0432\u043d\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u044f\u0437\u044b\u043a\u0430", "LabelSubtitlePlaybackMode": "\u0420\u0435\u0436\u0438\u043c \u0441\u0443\u0431\u0442\u0438\u0442\u0440\u043e\u0432:", "LabelDownloadLanguages": "\u0417\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c\u044b\u0435 \u044f\u0437\u044b\u043a\u0438:", @@ -1298,14 +1298,14 @@ "LabelDateAddedBehaviorHelp": "\u0415\u0441\u043b\u0438 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043f\u0440\u0438\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0432 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0445, \u0442\u043e \u043e\u043d\u043e \u0432\u0441\u0435\u0433\u0434\u0430 \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043f\u0440\u0435\u0436\u0434\u0435 \u043b\u044e\u0431\u043e\u0433\u043e \u0438\u0437 \u0434\u0430\u043d\u043d\u044b\u0445 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u043e\u0432.", "LabelNumberTrailerToPlay": "\u0427\u0438\u0441\u043b\u043e \u0442\u0440\u0435\u0439\u043b\u0435\u0440\u043e\u0432 \u0434\u043b\u044f \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u044f:", "TitleDevices": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430", - "TabCameraUpload": "\u0421 \u043a\u0430\u043c\u0435\u0440\u044b", + "TabCameraUpload": "\u041a\u0430\u043c\u0435\u0440\u044b", "TabDevices": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430", - "HeaderCameraUploadHelp": "\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0430 \u043e\u0442\u0441\u043d\u044f\u0442\u044b\u0445 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0439 \u0438 \u0432\u0438\u0434\u0435\u043e \u0438\u0437 \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u044b\u0445 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432 \u0432 Emby.", - "MessageNoDevicesSupportCameraUpload": "\u041d\u0435\u0442 \u043a\u0430\u043a\u0438\u0445-\u043b\u0438\u0431\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432, \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u044e\u0449\u0438\u0445 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0443 \u0441 \u043a\u0430\u043c\u0435\u0440\u044b.", - "LabelCameraUploadPath": "\u041f\u0443\u0442\u044c \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c\u043e\u0433\u043e \u0441 \u043a\u0430\u043c\u0435\u0440\u044b:", - "LabelCameraUploadPathHelp": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043d\u0435\u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439 \u043f\u0443\u0442\u044c, \u043f\u043e \u0436\u0435\u043b\u0430\u043d\u0438\u044e. \u0415\u0441\u043b\u0438 \u043d\u0435 \u0437\u0430\u0434\u0430\u043d\u043e, \u0442\u043e \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0430 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u0430\u044f \u043f\u0430\u043f\u043a\u0430. \u0415\u0441\u043b\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043d\u0435\u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439 \u043f\u0443\u0442\u044c, \u0442\u043e \u0435\u0433\u043e \u043d\u0443\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u0430\u043a\u0436\u0435 \u0432 \u043e\u0431\u043b\u0430\u0441\u0442\u0438 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043c\u0435\u0434\u0438\u0430\u0442\u0435\u043a\u0438.", + "HeaderCameraUploadHelp": "\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u0432\u044b\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u043d\u0438\u0435 \u043e\u0442\u0441\u043d\u044f\u0442\u044b\u0445 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0439 \u0438 \u0432\u0438\u0434\u0435\u043e \u0438\u0437 \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u044b\u0445 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432 \u0432 Emby.", + "MessageNoDevicesSupportCameraUpload": "\u041d\u0435\u0442 \u043a\u0430\u043a\u0438\u0445-\u043b\u0438\u0431\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432, \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u044e\u0449\u0438\u0445 \u0432\u044b\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u043d\u0438\u0435 \u0441 \u043a\u0430\u043c\u0435\u0440\u044b.", + "LabelCameraUploadPath": "\u041f\u0443\u0442\u044c \u0432\u044b\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u0435\u043c\u043e\u0433\u043e \u0441 \u043a\u0430\u043c\u0435\u0440\u044b:", + "LabelCameraUploadPathHelp": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043d\u0435\u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439 \u043f\u0443\u0442\u044c \u0432\u044b\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u043d\u0438\u044f, \u043f\u043e \u0436\u0435\u043b\u0430\u043d\u0438\u044e. \u0415\u0441\u043b\u0438 \u043d\u0435 \u0437\u0430\u0434\u0430\u043d\u043e, \u0442\u043e \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0430 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u0430\u044f \u043f\u0430\u043f\u043a\u0430. \u0415\u0441\u043b\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043d\u0435\u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439 \u043f\u0443\u0442\u044c, \u0442\u043e \u0435\u0433\u043e \u0442\u0430\u043a\u0436\u0435 \u043d\u0430\u0434\u043e \u0431\u0443\u0434\u0435\u0442 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0432 \u043e\u0431\u043b\u0430\u0441\u0442\u0438 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043c\u0435\u0434\u0438\u0430\u0442\u0435\u043a\u0438.", "LabelCreateCameraUploadSubfolder": "\u0421\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u043f\u043e\u0434\u043f\u0430\u043f\u043a\u0443 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430", - "LabelCreateCameraUploadSubfolderHelp": "\u0421\u043f\u0435\u0446\u0438\u0444\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u043f\u0430\u043f\u043a\u0438 \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u044b \u0434\u043b\u044f \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u043f\u0440\u0438 \u0449\u0435\u043b\u0447\u043a\u0435 \u043d\u0430 \u043d\u0451\u043c \u0441\u043e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \"\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\".", + "LabelCreateCameraUploadSubfolderHelp": "\u041d\u0435\u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0435 \u043f\u0430\u043f\u043a\u0438 \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u044b \u0434\u043b\u044f \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u043f\u0440\u0438 \u0449\u0435\u043b\u0447\u043a\u0435 \u043d\u0430 \u043d\u0451\u043c \u0441\u043e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \"\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\".", "LabelCustomDeviceDisplayName": "\u041e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u043c\u043e\u0435 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435:", "LabelCustomDeviceDisplayNameHelp": "\u041f\u0440\u0438\u0432\u0435\u0434\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u043c\u043e\u0435 \u0438\u043c\u044f \u0434\u043b\u044f \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0438\u043b\u0438 \u043e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043f\u043e\u043b\u0435 \u043d\u0435\u0437\u0430\u043f\u043e\u043b\u043d\u0435\u043d\u043d\u044b\u043c, \u0447\u0442\u043e\u0431\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0438\u043c\u044f, \u0432\u044b\u0434\u0430\u043d\u043d\u043e\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u043c.", "HeaderInviteUser": "\u041f\u0440\u0438\u0433\u043b\u0430\u0448\u0435\u043d\u0438\u0435 \u0434\u043b\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f", @@ -1326,7 +1326,7 @@ "HeaderSchedule": "\u0420\u0430\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0435", "OptionEveryday": "\u0415\u0436\u0435\u0434\u043d\u0435\u0432\u043d\u043e", "OptionWeekdays": "\u0412 \u0431\u0443\u0434\u043d\u0438", - "OptionWeekends": "\u0412 \u0432\u044b\u0445\u043e\u0434\u043d\u044b\u0435", + "OptionWeekends": "\u0412\u044b\u0445\u043e\u0434\u043d\u044b\u0435", "MessageProfileInfoSynced": "\u0414\u0430\u043d\u043d\u044b\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u043e\u0433\u043e \u043f\u0440\u043e\u0444\u0438\u043b\u044f \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u044b \u0441 Emby Connect.", "HeaderOptionalLinkEmbyAccount": "\u041d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e: \u0421\u0432\u044f\u0437\u044b\u0432\u0430\u043d\u0438\u0435 \u0432\u0430\u0448\u0435\u0439 \u0443\u0447\u0451\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 Emby", "ButtonTrailerReel": "\u0421\u043a\u043b\u0435\u0438\u0442\u044c \u0442\u0440\u0435\u0439\u043b\u0435\u0440\u044b", @@ -1345,7 +1345,7 @@ "ButtonConfigurePassword": "\u041d\u0430\u0437\u043d\u0430\u0447\u0438\u0442\u044c \u043f\u0430\u0440\u043e\u043b\u044c", "HeaderDashboardUserPassword": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0435 \u043f\u0430\u0440\u043e\u043b\u0438 \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u0432 \u043f\u0440\u0435\u0434\u0435\u043b\u0430\u0445 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u043b\u0438\u0447\u043d\u043e\u0433\u043e \u043f\u0440\u043e\u0444\u0438\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f.", "HeaderLibraryAccess": "\u0414\u043e\u0441\u0442\u0443\u043f \u043a \u043c\u0435\u0434\u0438\u0430\u0442\u0435\u043a\u0435", - "HeaderChannelAccess": "\u0414\u043e\u0441\u0442\u0443\u043f \u043a \u043a\u0430\u043d\u0430\u043b\u0430\u043c", + "HeaderChannelAccess": "\u0414\u043e\u0441\u0442\u0443\u043f \u043a\u043e \u043a\u0430\u043d\u0430\u043b\u0430\u043c", "HeaderLatestItems": "\u041f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0435 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b", "LabelSelectLastestItemsFolders": "\u041e\u0445\u0432\u0430\u0442\u044b\u0432\u0430\u043d\u0438\u0435 \u043c\u0435\u0434\u0438\u0430\u0434\u0430\u043d\u043d\u044b\u0445 \u0438\u0437 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u0440\u0430\u0437\u0434\u0435\u043b\u043e\u0432 \u0432 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0445 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u0445", "HeaderShareMediaFolders": "\u041e\u0431\u0449\u0438\u0439 \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u043c\u0435\u0434\u0438\u0430\u043f\u0430\u043f\u043a\u0430\u043c", @@ -1384,5 +1384,7 @@ "HeaderUpcomingPrograms": "\u041e\u0436\u0438\u0434\u0430\u0435\u043c\u044b\u0435 \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438", "ButtonMoreItems": "\u0415\u0449\u0451...", "LabelShowLibraryTileNames": "\u041f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u044f \u043f\u043b\u0438\u0442\u043e\u043a \u043c\u0435\u0434\u0438\u0430\u0442\u0435\u043a\u0438", - "LabelShowLibraryTileNamesHelp": "\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442\u0441\u044f, \u0431\u0443\u0434\u0443\u0442 \u043b\u0438 \u043d\u0430\u0434\u043f\u0438\u0441\u0438 \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u044b \u043f\u043e\u0434 \u043f\u043b\u0438\u0442\u043a\u0430\u043c\u0438 \u043c\u0435\u0434\u0438\u0430\u0442\u0435\u043a\u0438 \u043d\u0430 \u0433\u043b\u0430\u0432\u043d\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435." + "LabelShowLibraryTileNamesHelp": "\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442\u0441\u044f, \u0431\u0443\u0434\u0443\u0442 \u043b\u0438 \u043d\u0430\u0434\u043f\u0438\u0441\u0438 \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u044b \u043f\u043e\u0434 \u043f\u043b\u0438\u0442\u043a\u0430\u043c\u0438 \u043c\u0435\u0434\u0438\u0430\u0442\u0435\u043a\u0438 \u043d\u0430 \u0433\u043b\u0430\u0432\u043d\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435.", + "OptionEnableTranscodingThrottle": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0440\u0435\u0433\u0443\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435", + "OptionEnableTranscodingThrottleHelp": "\u0420\u0435\u0433\u0443\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0431\u0443\u0434\u0435\u0442 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u044c \u043f\u0435\u0440\u0435\u043a\u043e\u0434\u0438\u0440\u043e\u0432\u043a\u0438 \u0434\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u0441\u0432\u0435\u0441\u0442\u0438 \u043a \u043c\u0438\u043d\u0438\u043c\u0443\u043c\u0443 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0443 \u0446\u0435\u043d\u0442\u0440\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u0440\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0432\u043e \u0432\u0440\u0435\u043c\u044f \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u044f." } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/sl_SI.json b/MediaBrowser.Server.Implementations/Localization/Server/sl_SI.json index a5a48ff67a..86cb406b53 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/sl_SI.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/sl_SI.json @@ -1384,5 +1384,7 @@ "HeaderUpcomingPrograms": "Upcoming Programs", "ButtonMoreItems": "More...", "LabelShowLibraryTileNames": "Show library tile names", - "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page" + "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page", + "OptionEnableTranscodingThrottle": "Enable throttling", + "OptionEnableTranscodingThrottleHelp": "Throttling will automatically adjust transcoding speed in order to minimize server cpu utilization during playback." } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/sv.json b/MediaBrowser.Server.Implementations/Localization/Server/sv.json index 3f30cf4712..390ecb170c 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/sv.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/sv.json @@ -1384,5 +1384,7 @@ "HeaderUpcomingPrograms": "Upcoming Programs", "ButtonMoreItems": "Mer...", "LabelShowLibraryTileNames": "Show library tile names", - "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page" + "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page", + "OptionEnableTranscodingThrottle": "Enable throttling", + "OptionEnableTranscodingThrottleHelp": "Throttling will automatically adjust transcoding speed in order to minimize server cpu utilization during playback." } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/tr.json b/MediaBrowser.Server.Implementations/Localization/Server/tr.json index bf02c83dff..96b8db8fc8 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/tr.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/tr.json @@ -1384,5 +1384,7 @@ "HeaderUpcomingPrograms": "Upcoming Programs", "ButtonMoreItems": "More...", "LabelShowLibraryTileNames": "Show library tile names", - "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page" + "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page", + "OptionEnableTranscodingThrottle": "Enable throttling", + "OptionEnableTranscodingThrottleHelp": "Throttling will automatically adjust transcoding speed in order to minimize server cpu utilization during playback." } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/uk.json b/MediaBrowser.Server.Implementations/Localization/Server/uk.json index 37132dc0a6..1f6da07001 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/uk.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/uk.json @@ -1384,5 +1384,7 @@ "HeaderUpcomingPrograms": "Upcoming Programs", "ButtonMoreItems": "More...", "LabelShowLibraryTileNames": "Show library tile names", - "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page" + "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page", + "OptionEnableTranscodingThrottle": "Enable throttling", + "OptionEnableTranscodingThrottleHelp": "Throttling will automatically adjust transcoding speed in order to minimize server cpu utilization during playback." } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/vi.json b/MediaBrowser.Server.Implementations/Localization/Server/vi.json index f9dc72b5e1..8183e46018 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/vi.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/vi.json @@ -1384,5 +1384,7 @@ "HeaderUpcomingPrograms": "Upcoming Programs", "ButtonMoreItems": "More...", "LabelShowLibraryTileNames": "Show library tile names", - "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page" + "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page", + "OptionEnableTranscodingThrottle": "Enable throttling", + "OptionEnableTranscodingThrottleHelp": "Throttling will automatically adjust transcoding speed in order to minimize server cpu utilization during playback." } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/zh_CN.json b/MediaBrowser.Server.Implementations/Localization/Server/zh_CN.json index ac48f5074a..b2c2169602 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/zh_CN.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/zh_CN.json @@ -1384,5 +1384,7 @@ "HeaderUpcomingPrograms": "Upcoming Programs", "ButtonMoreItems": "\u66f4\u591a...", "LabelShowLibraryTileNames": "Show library tile names", - "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page" + "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page", + "OptionEnableTranscodingThrottle": "Enable throttling", + "OptionEnableTranscodingThrottleHelp": "Throttling will automatically adjust transcoding speed in order to minimize server cpu utilization during playback." } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/zh_TW.json b/MediaBrowser.Server.Implementations/Localization/Server/zh_TW.json index ffd1c3fe46..d957eb2cd9 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/zh_TW.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/zh_TW.json @@ -1384,5 +1384,7 @@ "HeaderUpcomingPrograms": "Upcoming Programs", "ButtonMoreItems": "More...", "LabelShowLibraryTileNames": "Show library tile names", - "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page" + "LabelShowLibraryTileNamesHelp": "Determines if labels will be displayed underneath library tiles on the home page", + "OptionEnableTranscodingThrottle": "Enable throttling", + "OptionEnableTranscodingThrottleHelp": "Throttling will automatically adjust transcoding speed in order to minimize server cpu utilization during playback." } \ No newline at end of file From eaf70589ae9469616596ee951e50e83941495b9e Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 30 Mar 2015 22:52:18 -0400 Subject: [PATCH 28/52] update sync folder structure --- .../Sync/MediaSync.cs | 68 +++++++++++++------ 1 file changed, 46 insertions(+), 22 deletions(-) diff --git a/MediaBrowser.Server.Implementations/Sync/MediaSync.cs b/MediaBrowser.Server.Implementations/Sync/MediaSync.cs index ae8b228275..e7333939e3 100644 --- a/MediaBrowser.Server.Implementations/Sync/MediaSync.cs +++ b/MediaBrowser.Server.Implementations/Sync/MediaSync.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Common.IO; +using System.Globalization; +using MediaBrowser.Common.IO; using MediaBrowser.Common.Progress; using MediaBrowser.Controller; using MediaBrowser.Controller.Sync; @@ -142,8 +143,9 @@ namespace MediaBrowser.Server.Implementations.Sync { var libraryItem = jobItem.Item; var internalSyncJobItem = _syncManager.GetJobItem(jobItem.SyncJobItemId); + var internalSyncJob = _syncManager.GetJob(jobItem.SyncJobId); - var localItem = CreateLocalItem(provider, jobItem, target, libraryItem, serverId, serverName, jobItem.OriginalFileName); + var localItem = CreateLocalItem(provider, jobItem, internalSyncJob, target, libraryItem, serverId, serverName, jobItem.OriginalFileName); await _syncManager.ReportSyncJobItemTransferBeginning(internalSyncJobItem.Id); @@ -329,9 +331,9 @@ namespace MediaBrowser.Server.Implementations.Sync } } - public LocalItem CreateLocalItem(IServerSyncProvider provider, SyncedItem syncedItem, SyncTarget target, BaseItemDto libraryItem, string serverId, string serverName, string originalFileName) + public LocalItem CreateLocalItem(IServerSyncProvider provider, SyncedItem syncedItem, SyncJob job, SyncTarget target, BaseItemDto libraryItem, string serverId, string serverName, string originalFileName) { - var path = GetDirectoryPath(provider, syncedItem, libraryItem, serverId, serverName); + var path = GetDirectoryPath(provider, job, syncedItem, libraryItem, serverName); path.Add(GetLocalFileName(provider, libraryItem, originalFileName)); var localPath = provider.GetFullPath(path, target); @@ -352,29 +354,51 @@ namespace MediaBrowser.Server.Implementations.Sync }; } - private string GetSyncJobFolderName(SyncedItem syncedItem, IServerSyncProvider provider) + private List GetDirectoryPath(IServerSyncProvider provider, SyncJob job, SyncedItem syncedItem, BaseItemDto item, string serverName) { - var name = syncedItem.SyncJobName + "-" + syncedItem.SyncJobDateCreated - .ToLocalTime() - .ToString("g") - .Replace(" ", "-"); + var parts = new List + { + serverName + }; - name = GetValidFilename(provider, name); + var profileOption = _syncManager.GetProfileOptions(job.TargetId) + .FirstOrDefault(i => string.Equals(i.Id, job.Profile, StringComparison.OrdinalIgnoreCase)); - return name; - } + string name; - private List GetDirectoryPath(IServerSyncProvider provider, SyncedItem syncedItem, BaseItemDto item, string serverId, string serverName) - { - var parts = new List + if (profileOption != null && !string.IsNullOrWhiteSpace(profileOption.Name)) { - serverName, - GetSyncJobFolderName(syncedItem, provider) - }; + name = profileOption.Name; + + if (job.Bitrate.HasValue) + { + name += "-" + job.Bitrate.Value.ToString(CultureInfo.InvariantCulture); + } + else + { + var qualityOption = _syncManager.GetQualityOptions(job.TargetId) + .FirstOrDefault(i => string.Equals(i.Id, job.Quality, StringComparison.OrdinalIgnoreCase)); + + if (qualityOption != null && !string.IsNullOrWhiteSpace(qualityOption.Name)) + { + name += "-" + qualityOption.Name; + } + } + } + else + { + name = syncedItem.SyncJobName + "-" + syncedItem.SyncJobDateCreated + .ToLocalTime() + .ToString("g") + .Replace(" ", "-"); + } + + name = GetValidFilename(provider, name); + parts.Add(name); if (item.IsType("episode")) { - //parts.Add("TV"); + parts.Add("TV"); if (!string.IsNullOrWhiteSpace(item.SeriesName)) { parts.Add(item.SeriesName); @@ -382,12 +406,12 @@ namespace MediaBrowser.Server.Implementations.Sync } else if (item.IsVideo) { - //parts.Add("Videos"); + parts.Add("Videos"); parts.Add(item.Name); } else if (item.IsAudio) { - //parts.Add("Music"); + parts.Add("Music"); if (!string.IsNullOrWhiteSpace(item.AlbumArtist)) { @@ -401,7 +425,7 @@ namespace MediaBrowser.Server.Implementations.Sync } else if (string.Equals(item.MediaType, MediaType.Photo, StringComparison.OrdinalIgnoreCase)) { - //parts.Add("Photos"); + parts.Add("Photos"); if (!string.IsNullOrWhiteSpace(item.Album)) { From a025f4eefaf43c27f33521239d99c47d292728f5 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 31 Mar 2015 12:24:16 -0400 Subject: [PATCH 29/52] sync updates --- MediaBrowser.Api/BaseApiService.cs | 11 ++ .../Playback/BaseStreamingService.cs | 2 +- MediaBrowser.Api/Playback/MediaInfoService.cs | 62 ++++++---- .../Playback/TranscodingThrottler.cs | 2 +- .../UserLibrary/UserLibraryService.cs | 8 ++ .../MediaEncoding/ISubtitleEncoder.cs | 8 +- .../MediaEncoding/MediaStreamSelector.cs | 77 ++++++++++++- MediaBrowser.Dlna/Didl/DidlBuilder.cs | 4 +- MediaBrowser.Dlna/PlayTo/PlayToController.cs | 4 +- .../Encoder/BaseEncoder.cs | 2 +- .../Subtitles/SubtitleEncoder.cs | 109 ++++++++++++------ .../Configuration/UserConfiguration.cs | 1 + MediaBrowser.Model/Dlna/StreamBuilder.cs | 59 +++++++++- MediaBrowser.Model/Dlna/StreamInfo.cs | 74 +++++++----- MediaBrowser.Model/Entities/MediaStream.cs | 6 + .../MediaInfo/SubtitleScheduledTask.cs | 9 +- .../Dto/DtoService.cs | 8 +- .../Library/MediaSourceManager.cs | 31 +++-- .../Library/UserViewManager.cs | 2 +- .../LiveTv/LiveTvMediaSourceProvider.cs | 6 +- .../Localization/Server/server.json | 1 + .../Sync/CloudSyncProfile.cs | 5 +- .../Sync/SyncJobProcessor.cs | 4 +- .../ApplicationHost.cs | 2 +- 24 files changed, 365 insertions(+), 132 deletions(-) diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs index 4465be97a2..3364c3c6be 100644 --- a/MediaBrowser.Api/BaseApiService.cs +++ b/MediaBrowser.Api/BaseApiService.cs @@ -73,6 +73,17 @@ namespace MediaBrowser.Api return ResultFactory.GetOptimizedResultUsingCache(Request, cacheKey, lastDateModified, cacheDuration, factoryFn); } + /// + /// Infers the server address from the url + /// + /// + protected string GetServerAddress() + { + var index = Request.AbsoluteUri.IndexOf(Request.PathInfo, StringComparison.OrdinalIgnoreCase); + + return Request.AbsoluteUri.Substring(0, index); + } + protected void AssertCanUpdateUser(IUserManager userManager, string userId) { var auth = AuthorizationContext.GetAuthorizationInfo(Request); diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 8c4bcf0a31..75321f872c 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -699,7 +699,7 @@ namespace MediaBrowser.Api.Playback if (!string.IsNullOrEmpty(state.SubtitleStream.Language)) { - var charenc = SubtitleEncoder.GetSubtitleFileCharacterSet(subtitlePath); + var charenc = SubtitleEncoder.GetSubtitleFileCharacterSet(subtitlePath, state.MediaSource.Protocol, CancellationToken.None).Result; if (!string.IsNullOrEmpty(charenc)) { diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs index 6eba195453..0930c00024 100644 --- a/MediaBrowser.Api/Playback/MediaInfoService.cs +++ b/MediaBrowser.Api/Playback/MediaInfoService.cs @@ -59,6 +59,9 @@ namespace MediaBrowser.Api.Playback [ApiMember(Name = "MediaSourceId", Description = "The media version id, if playing an alternate version", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public string MediaSourceId { get; set; } + + [ApiMember(Name = "LiveStreamId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string LiveStreamId { get; set; } } [Route("/LiveStreams/Open", "POST", Summary = "Opens a media source")] @@ -142,7 +145,7 @@ namespace MediaBrowser.Api.Playback public async Task Post(GetPostedPlaybackInfo request) { - var info = await GetPlaybackInfo(request.Id, request.UserId, request.MediaSourceId).ConfigureAwait(false); + var info = await GetPlaybackInfo(request.Id, request.UserId, request.MediaSourceId, request.LiveStreamId).ConfigureAwait(false); var authInfo = AuthorizationContext.GetAuthorizationInfo(Request); var profile = request.DeviceProfile; @@ -164,29 +167,37 @@ namespace MediaBrowser.Api.Playback return ToOptimizedResult(info); } - private async Task GetPlaybackInfo(string id, string userId, string mediaSourceId = null) + private async Task GetPlaybackInfo(string id, string userId, string mediaSourceId = null, string liveStreamId = null) { var result = new PlaybackInfoResponse(); - IEnumerable mediaSources; - - try - { - mediaSources = await _mediaSourceManager.GetPlayackMediaSources(id, userId, true, CancellationToken.None).ConfigureAwait(false); - } - catch (PlaybackException ex) + if (string.IsNullOrWhiteSpace(liveStreamId)) { - mediaSources = new List(); - result.ErrorCode = ex.ErrorCode; - } + IEnumerable mediaSources; + try + { + mediaSources = await _mediaSourceManager.GetPlayackMediaSources(id, userId, true, CancellationToken.None).ConfigureAwait(false); + } + catch (PlaybackException ex) + { + mediaSources = new List(); + result.ErrorCode = ex.ErrorCode; + } - result.MediaSources = mediaSources.ToList(); + result.MediaSources = mediaSources.ToList(); - if (!string.IsNullOrWhiteSpace(mediaSourceId)) + if (!string.IsNullOrWhiteSpace(mediaSourceId)) + { + result.MediaSources = result.MediaSources + .Where(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase)) + .ToList(); + } + } + else { - result.MediaSources = result.MediaSources - .Where(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase)) - .ToList(); + var mediaSource = await _mediaSourceManager.GetLiveStream(liveStreamId, CancellationToken.None).ConfigureAwait(false); + + result.MediaSources = new List { mediaSource }; } if (result.MediaSources.Count == 0) @@ -236,6 +247,8 @@ namespace MediaBrowser.Api.Playback { var streamBuilder = new StreamBuilder(); + var baseUrl = GetServerAddress(); + var options = new VideoOptions { MediaSources = new List { mediaSource }, @@ -275,7 +288,7 @@ namespace MediaBrowser.Api.Playback if (streamInfo != null) { - SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token); + SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, baseUrl, auth.Token); } } @@ -293,7 +306,7 @@ namespace MediaBrowser.Api.Playback if (streamInfo != null) { - SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token); + SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, baseUrl, auth.Token); } } @@ -307,21 +320,22 @@ namespace MediaBrowser.Api.Playback if (streamInfo != null && streamInfo.PlayMethod == PlayMethod.Transcode) { streamInfo.StartPositionTicks = startTimeTicks; - mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).TrimStart('-'); + mediaSource.TranscodingUrl = streamInfo.ToUrl(baseUrl, auth.Token); mediaSource.TranscodingContainer = streamInfo.Container; mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol; } if (streamInfo != null) { - SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token); + SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, baseUrl, auth.Token); } } } - private void SetDeviceSpecificSubtitleInfo(StreamInfo info, MediaSourceInfo mediaSource, string accessToken) + private void SetDeviceSpecificSubtitleInfo(StreamInfo info, MediaSourceInfo mediaSource, string baseUrl, string accessToken) { - var profiles = info.GetSubtitleProfiles(false, "-", accessToken); + var profiles = info.GetSubtitleProfiles(false, baseUrl, accessToken); + mediaSource.DefaultSubtitleStreamIndex = info.SubtitleStreamIndex; foreach (var profile in profiles) { @@ -333,7 +347,7 @@ namespace MediaBrowser.Api.Playback if (profile.DeliveryMethod == SubtitleDeliveryMethod.External) { - stream.DeliveryUrl = profile.Url.TrimStart('-'); + stream.DeliveryUrl = profile.Url; } } } diff --git a/MediaBrowser.Api/Playback/TranscodingThrottler.cs b/MediaBrowser.Api/Playback/TranscodingThrottler.cs index 58cfa086e3..ff79bb48f4 100644 --- a/MediaBrowser.Api/Playback/TranscodingThrottler.cs +++ b/MediaBrowser.Api/Playback/TranscodingThrottler.cs @@ -42,7 +42,7 @@ namespace MediaBrowser.Api.Playback var options = GetOptions(); - if (options.EnableThrottling && IsThrottleAllowed(_job, options.ThrottleThresholdSeconds)) + if (/*options.EnableThrottling &&*/ IsThrottleAllowed(_job, options.ThrottleThresholdSeconds)) { PauseTranscoding(); } diff --git a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs index cdfd00ce9e..38eae25779 100644 --- a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs +++ b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs @@ -304,6 +304,14 @@ namespace MediaBrowser.Api.UserLibrary { var user = _userManager.GetUserById(request.UserId); + if (!request.IsPlayed.HasValue) + { + if (user.Configuration.HidePlayedInLatest) + { + request.IsPlayed = false; + } + } + var list = _userViewManager.GetLatestItems(new LatestItemsQuery { GroupItems = request.GroupItems, diff --git a/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs b/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs index 37c2bf4d2e..e4a2cd007b 100644 --- a/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs @@ -1,4 +1,5 @@ -using System.IO; +using MediaBrowser.Model.MediaInfo; +using System.IO; using System.Threading; using System.Threading.Tasks; @@ -47,8 +48,9 @@ namespace MediaBrowser.Controller.MediaEncoding /// Gets the subtitle language encoding parameter. /// /// The path. + /// The protocol. + /// The cancellation token. /// System.String. - string GetSubtitleFileCharacterSet(string path); - + Task GetSubtitleFileCharacterSet(string path, MediaProtocol protocol, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Controller/MediaEncoding/MediaStreamSelector.cs b/MediaBrowser.Controller/MediaEncoding/MediaStreamSelector.cs index 57fddb2b1f..a006a17230 100644 --- a/MediaBrowser.Controller/MediaEncoding/MediaStreamSelector.cs +++ b/MediaBrowser.Controller/MediaEncoding/MediaStreamSelector.cs @@ -41,8 +41,6 @@ namespace MediaBrowser.Controller.MediaEncoding streams = GetSortedStreams(streams, MediaStreamType.Subtitle, preferredLanguages) .ToList(); - var full = streams.Where(s => !s.IsForced); - MediaStream stream = null; if (mode == SubtitlePlaybackMode.None) @@ -55,13 +53,13 @@ namespace MediaBrowser.Controller.MediaEncoding // if the audio language is not understood by the user, load their preferred subs, if there are any if (!ContainsOrdinal(preferredLanguages, audioTrackLanguage)) { - stream = full.FirstOrDefault(s => ContainsOrdinal(preferredLanguages, s.Language)); + stream = streams.Where(s => !s.IsForced).FirstOrDefault(s => ContainsOrdinal(preferredLanguages, s.Language)); } } else if (mode == SubtitlePlaybackMode.Always) { // always load the most suitable full subtitles - stream = full.FirstOrDefault(); + stream = streams.FirstOrDefault(s => !s.IsForced); } // load forced subs if we have found no suitable full subtitles @@ -97,6 +95,77 @@ namespace MediaBrowser.Controller.MediaEncoding .ThenBy(i => i.Index); } + public static void SetSubtitleStreamScores(List streams, + List preferredLanguages, + SubtitlePlaybackMode mode, + string audioTrackLanguage) + { + if (mode == SubtitlePlaybackMode.None) + { + return; + } + + streams = GetSortedStreams(streams, MediaStreamType.Subtitle, preferredLanguages) + .ToList(); + + var filteredStreams = new List(); + + if (mode == SubtitlePlaybackMode.Default) + { + // if the audio language is not understood by the user, load their preferred subs, if there are any + if (!ContainsOrdinal(preferredLanguages, audioTrackLanguage)) + { + filteredStreams = streams.Where(s => !s.IsForced && ContainsOrdinal(preferredLanguages, s.Language)) + .ToList(); + } + } + else if (mode == SubtitlePlaybackMode.Always) + { + // always load the most suitable full subtitles + filteredStreams = streams.Where(s => !s.IsForced) + .ToList(); + } + + // load forced subs if we have found no suitable full subtitles + if (filteredStreams.Count == 0) + { + filteredStreams = streams + .Where(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase)) + .ToList(); + } + + foreach (var stream in filteredStreams) + { + stream.Score = GetSubtitleScore(stream, preferredLanguages); + } + } + + private static int GetSubtitleScore(MediaStream stream, List languagePreferences) + { + var values = new List(); + + var index = languagePreferences.FindIndex(l => string.Equals(stream.Language, l, StringComparison.OrdinalIgnoreCase)); + + values.Add(index == -1 ? 0 : 100 - index); + + values.Add(stream.IsDefault ? 1 : 0); + values.Add(stream.SupportsExternalStream ? 1 : 0); + values.Add(stream.IsTextSubtitleStream ? 1 : 0); + values.Add(stream.IsExternal ? 1 : 0); + + values.Reverse(); + var scale = 1; + var score = 0; + + foreach (var value in values) + { + score += scale * (value + 1); + scale *= 10; + } + + return score; + } + private static int GetBooleanOrderBy(bool value) { return value ? 0 : 1; diff --git a/MediaBrowser.Dlna/Didl/DidlBuilder.cs b/MediaBrowser.Dlna/Didl/DidlBuilder.cs index b364414d10..3b1cdb5428 100644 --- a/MediaBrowser.Dlna/Didl/DidlBuilder.cs +++ b/MediaBrowser.Dlna/Didl/DidlBuilder.cs @@ -124,7 +124,7 @@ namespace MediaBrowser.Dlna.Didl { if (streamInfo == null) { - var sources = _user == null ? video.GetMediaSources(true).ToList() : _mediaSourceManager.GetStaticMediaSources(video, true, _user).ToList(); + var sources = _user == null ? _mediaSourceManager.GetStaticMediaSources(video, true).ToList() : _mediaSourceManager.GetStaticMediaSources(video, true, _user).ToList(); streamInfo = new StreamBuilder().BuildVideoItem(new VideoOptions { @@ -351,7 +351,7 @@ namespace MediaBrowser.Dlna.Didl if (streamInfo == null) { - var sources = _user == null ? audio.GetMediaSources(true).ToList() : _mediaSourceManager.GetStaticMediaSources(audio, true, _user).ToList(); + var sources = _user == null ? _mediaSourceManager.GetStaticMediaSources(audio, true).ToList() : _mediaSourceManager.GetStaticMediaSources(audio, true, _user).ToList(); streamInfo = new StreamBuilder().BuildAudioItem(new AudioOptions { diff --git a/MediaBrowser.Dlna/PlayTo/PlayToController.cs b/MediaBrowser.Dlna/PlayTo/PlayToController.cs index 3eb091a194..38c0f71cc7 100644 --- a/MediaBrowser.Dlna/PlayTo/PlayToController.cs +++ b/MediaBrowser.Dlna/PlayTo/PlayToController.cs @@ -467,10 +467,10 @@ namespace MediaBrowser.Dlna.PlayTo var profile = _dlnaManager.GetProfile(deviceInfo.ToDeviceIdentification()) ?? _dlnaManager.GetDefaultProfile(); - + var hasMediaSources = item as IHasMediaSources; var mediaSources = hasMediaSources != null - ? (user == null ? hasMediaSources.GetMediaSources(true) : _mediaSourceManager.GetStaticMediaSources(hasMediaSources, true, user)).ToList() + ? (user == null ? _mediaSourceManager.GetStaticMediaSources(hasMediaSources, true) : _mediaSourceManager.GetStaticMediaSources(hasMediaSources, true, user)).ToList() : new List(); var playlistItem = GetPlaylistItem(item, mediaSources, profile, _session.DeviceId, mediaSourceId, audioStreamIndex, subtitleStreamIndex); diff --git a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs index 8266f4d3ad..78ac92f25b 100644 --- a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs @@ -951,7 +951,7 @@ namespace MediaBrowser.MediaEncoding.Encoder if (!string.IsNullOrEmpty(state.SubtitleStream.Language)) { - var charenc = SubtitleEncoder.GetSubtitleFileCharacterSet(subtitlePath); + var charenc = SubtitleEncoder.GetSubtitleFileCharacterSet(subtitlePath, state.MediaSource.Protocol, CancellationToken.None).Result; if (!string.IsNullOrEmpty(charenc)) { diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index 7d74c51bad..14d3e72840 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -1,6 +1,7 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; @@ -29,8 +30,10 @@ namespace MediaBrowser.MediaEncoding.Subtitles private readonly IFileSystem _fileSystem; private readonly IMediaEncoder _mediaEncoder; private readonly IJsonSerializer _json; + private readonly IHttpClient _httpClient; + private readonly IMediaSourceManager _mediaSourceManager; - public SubtitleEncoder(ILibraryManager libraryManager, ILogger logger, IApplicationPaths appPaths, IFileSystem fileSystem, IMediaEncoder mediaEncoder, IJsonSerializer json) + public SubtitleEncoder(ILibraryManager libraryManager, ILogger logger, IApplicationPaths appPaths, IFileSystem fileSystem, IMediaEncoder mediaEncoder, IJsonSerializer json, IHttpClient httpClient, IMediaSourceManager mediaSourceManager) { _libraryManager = libraryManager; _logger = logger; @@ -38,6 +41,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles _fileSystem = fileSystem; _mediaEncoder = mediaEncoder; _json = json; + _httpClient = httpClient; + _mediaSourceManager = mediaSourceManager; } private string SubtitleCachePath @@ -127,9 +132,9 @@ namespace MediaBrowser.MediaEncoding.Subtitles int subtitleStreamIndex, CancellationToken cancellationToken) { - var item = (Video)_libraryManager.GetItemById(new Guid(itemId)); + var mediaSources = await _mediaSourceManager.GetPlayackMediaSources(itemId, false, cancellationToken).ConfigureAwait(false); - var mediaSource = item.GetMediaSources(false) + var mediaSource = mediaSources .First(i => string.Equals(i.Id, mediaSourceId)); var subtitleStream = mediaSource.MediaStreams @@ -149,20 +154,20 @@ namespace MediaBrowser.MediaEncoding.Subtitles var fileInfo = await GetReadableFile(mediaSource.Path, inputFiles, mediaSource.Protocol, subtitleStream, cancellationToken).ConfigureAwait(false); - var stream = await GetSubtitleStream(fileInfo.Item1, fileInfo.Item3).ConfigureAwait(false); + var stream = await GetSubtitleStream(fileInfo.Item1, fileInfo.Item2, fileInfo.Item4, cancellationToken).ConfigureAwait(false); - return new Tuple(stream, fileInfo.Item2); + return new Tuple(stream, fileInfo.Item3); } - private async Task GetSubtitleStream(string path, bool requiresCharset) + private async Task GetSubtitleStream(string path, MediaProtocol protocol, bool requiresCharset, CancellationToken cancellationToken) { if (requiresCharset) { - var charset = GetSubtitleFileCharacterSet(path); + var charset = await GetSubtitleFileCharacterSet(path, protocol, cancellationToken).ConfigureAwait(false); if (!string.IsNullOrEmpty(charset)) { - using (var fs = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true)) + using (var fs = await GetStream(path, protocol, cancellationToken).ConfigureAwait(false)) { using (var reader = new StreamReader(fs, GetEncoding(charset))) { @@ -196,7 +201,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles } } - private async Task> GetReadableFile(string mediaPath, + private async Task> GetReadableFile(string mediaPath, string[] inputFiles, MediaProtocol protocol, MediaStream subtitleStream, @@ -228,12 +233,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles } // Extract - var outputPath = GetSubtitleCachePath(mediaPath, subtitleStream.Index, "." + outputFormat); + var outputPath = GetSubtitleCachePath(mediaPath, protocol, subtitleStream.Index, "." + outputFormat); await ExtractTextSubtitle(inputFiles, protocol, subtitleStream.Index, outputCodec, outputPath, cancellationToken) .ConfigureAwait(false); - return new Tuple(outputPath, outputFormat, false); + return new Tuple(outputPath, MediaProtocol.File, outputFormat, false); } var currentFormat = (Path.GetExtension(subtitleStream.Path) ?? subtitleStream.Codec) @@ -242,14 +247,14 @@ namespace MediaBrowser.MediaEncoding.Subtitles if (GetReader(currentFormat, false) == null) { // Convert - var outputPath = GetSubtitleCachePath(mediaPath, subtitleStream.Index, ".srt"); + var outputPath = GetSubtitleCachePath(mediaPath, protocol, subtitleStream.Index, ".srt"); - await ConvertTextSubtitleToSrt(subtitleStream.Path, outputPath, cancellationToken).ConfigureAwait(false); + await ConvertTextSubtitleToSrt(subtitleStream.Path, protocol, outputPath, cancellationToken).ConfigureAwait(false); - return new Tuple(outputPath, "srt", true); + return new Tuple(outputPath, MediaProtocol.File, "srt", true); } - return new Tuple(subtitleStream.Path, currentFormat, true); + return new Tuple(subtitleStream.Path, protocol, currentFormat, true); } private async Task GetTrackInfo(Stream stream, @@ -336,10 +341,11 @@ namespace MediaBrowser.MediaEncoding.Subtitles /// Converts the text subtitle to SRT. /// /// The input path. + /// The input protocol. /// The output path. /// The cancellation token. /// Task. - public async Task ConvertTextSubtitleToSrt(string inputPath, string outputPath, CancellationToken cancellationToken) + private async Task ConvertTextSubtitleToSrt(string inputPath, MediaProtocol inputProtocol, string outputPath, CancellationToken cancellationToken) { var semaphore = GetLock(outputPath); @@ -349,7 +355,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles { if (!File.Exists(outputPath)) { - await ConvertTextSubtitleToSrtInternal(inputPath, outputPath).ConfigureAwait(false); + await ConvertTextSubtitleToSrtInternal(inputPath, inputProtocol, outputPath, cancellationToken).ConfigureAwait(false); } } finally @@ -362,13 +368,17 @@ namespace MediaBrowser.MediaEncoding.Subtitles /// Converts the text subtitle to SRT internal. /// /// The input path. + /// The input protocol. /// The output path. + /// The cancellation token. /// Task. - /// inputPath + /// + /// inputPath /// or - /// outputPath + /// outputPath + /// /// - private async Task ConvertTextSubtitleToSrtInternal(string inputPath, string outputPath) + private async Task ConvertTextSubtitleToSrtInternal(string inputPath, MediaProtocol inputProtocol, string outputPath, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(inputPath)) { @@ -382,7 +392,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); - var encodingParam = GetSubtitleFileCharacterSet(inputPath); + var encodingParam = await GetSubtitleFileCharacterSet(inputPath, inputProtocol, cancellationToken).ConfigureAwait(false); if (!string.IsNullOrEmpty(encodingParam)) { @@ -688,32 +698,41 @@ namespace MediaBrowser.MediaEncoding.Subtitles } } - private string GetSubtitleCachePath(string mediaPath, int subtitleStreamIndex, string outputSubtitleExtension) + private string GetSubtitleCachePath(string mediaPath, MediaProtocol protocol, int subtitleStreamIndex, string outputSubtitleExtension) { - var ticksParam = string.Empty; + if (protocol == MediaProtocol.File) + { + var ticksParam = string.Empty; + + var date = _fileSystem.GetLastWriteTimeUtc(mediaPath); - var date = _fileSystem.GetLastWriteTimeUtc(mediaPath); + var filename = (mediaPath + "_" + subtitleStreamIndex.ToString(CultureInfo.InvariantCulture) + "_" + date.Ticks.ToString(CultureInfo.InvariantCulture) + ticksParam).GetMD5() + outputSubtitleExtension; - var filename = (mediaPath + "_" + subtitleStreamIndex.ToString(CultureInfo.InvariantCulture) + "_" + date.Ticks.ToString(CultureInfo.InvariantCulture) + ticksParam).GetMD5() + outputSubtitleExtension; + var prefix = filename.Substring(0, 1); + + return Path.Combine(SubtitleCachePath, prefix, filename); + } + else + { + var filename = (mediaPath + "_" + subtitleStreamIndex.ToString(CultureInfo.InvariantCulture)).GetMD5() + outputSubtitleExtension; - var prefix = filename.Substring(0, 1); + var prefix = filename.Substring(0, 1); - return Path.Combine(SubtitleCachePath, prefix, filename); + return Path.Combine(SubtitleCachePath, prefix, filename); + } } - /// - /// Gets the subtitle language encoding param. - /// - /// The path. - /// System.String. - public string GetSubtitleFileCharacterSet(string path) + public async Task GetSubtitleFileCharacterSet(string path, MediaProtocol protocol, CancellationToken cancellationToken) { - if (GetFileEncoding(path).Equals(Encoding.UTF8)) + if (protocol == MediaProtocol.File) { - return string.Empty; + if (GetFileEncoding(path).Equals(Encoding.UTF8)) + { + return string.Empty; + } } - var charset = DetectCharset(path); + var charset = await DetectCharset(path, protocol, cancellationToken).ConfigureAwait(false); if (!string.IsNullOrWhiteSpace(charset)) { @@ -769,11 +788,11 @@ namespace MediaBrowser.MediaEncoding.Subtitles } } - private string DetectCharset(string path) + private async Task DetectCharset(string path, MediaProtocol protocol, CancellationToken cancellationToken) { try { - using (var file = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + using (var file = await GetStream(path, protocol, cancellationToken).ConfigureAwait(false)) { var detector = new CharsetDetector(); detector.Feed(file); @@ -819,5 +838,19 @@ namespace MediaBrowser.MediaEncoding.Subtitles // It's ok - anything aside from utf is ok since that's what we're looking for return Encoding.Default; } + + private async Task GetStream(string path, MediaProtocol protocol, CancellationToken cancellationToken) + { + if (protocol == MediaProtocol.Http) + { + return await _httpClient.Get(path, cancellationToken).ConfigureAwait(false); + } + if (protocol == MediaProtocol.File) + { + return _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + } + + throw new ArgumentOutOfRangeException("protocol"); + } } } diff --git a/MediaBrowser.Model/Configuration/UserConfiguration.cs b/MediaBrowser.Model/Configuration/UserConfiguration.cs index a78161140f..98641636ae 100644 --- a/MediaBrowser.Model/Configuration/UserConfiguration.cs +++ b/MediaBrowser.Model/Configuration/UserConfiguration.cs @@ -54,6 +54,7 @@ namespace MediaBrowser.Model.Configuration public string[] LatestItemsExcludes { get; set; } public bool HasMigratedToPolicy { get; set; } + public bool HidePlayedInLatest { get; set; } /// /// Initializes a new instance of the class. diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 1cc37de57c..b27138b4a7 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -282,6 +282,49 @@ namespace MediaBrowser.Model.Dlna return playMethods; } + private int? GetDefaultSubtitleStreamIndex(MediaSourceInfo item, SubtitleProfile[] subtitleProfiles) + { + int highestScore = -1; + + foreach (MediaStream stream in item.MediaStreams) + { + if (stream.Type == MediaStreamType.Subtitle && stream.Score.HasValue) + { + if (stream.Score.Value > highestScore) + { + highestScore = stream.Score.Value; + } + } + } + + List topStreams = new List(); + foreach (MediaStream stream in item.MediaStreams) + { + if (stream.Type == MediaStreamType.Subtitle && stream.Score.HasValue && stream.Score.Value == highestScore) + { + topStreams.Add(stream); + } + } + + // If multiple streams have an equal score, try to pick the most efficient one + if (topStreams.Count > 1) + { + foreach (MediaStream stream in topStreams) + { + foreach (SubtitleProfile profile in subtitleProfiles) + { + if (profile.Method == SubtitleDeliveryMethod.External && StringHelper.EqualsIgnoreCase(profile.Format, stream.Codec)) + { + return stream.Index; + } + } + } + } + + // If no optimization panned out, just use the original default + return item.DefaultSubtitleStreamIndex; + } + private StreamInfo BuildVideoItem(MediaSourceInfo item, VideoOptions options) { StreamInfo playlistItem = new StreamInfo @@ -294,7 +337,7 @@ namespace MediaBrowser.Model.Dlna DeviceProfile = options.Profile }; - playlistItem.SubtitleStreamIndex = options.SubtitleStreamIndex ?? item.DefaultSubtitleStreamIndex; + playlistItem.SubtitleStreamIndex = options.SubtitleStreamIndex ?? GetDefaultSubtitleStreamIndex(item, options.Profile.SubtitleProfiles); MediaStream subtitleStream = playlistItem.SubtitleStreamIndex.HasValue ? item.GetMediaStream(MediaStreamType.Subtitle, playlistItem.SubtitleStreamIndex.Value) : null; MediaStream audioStream = item.GetDefaultAudioStream(options.AudioStreamIndex ?? item.DefaultAudioStreamIndex); @@ -618,6 +661,8 @@ namespace MediaBrowser.Model.Dlna // Look for an external profile that matches the stream type (text/graphical) foreach (SubtitleProfile profile in subtitleProfiles) { + bool requiresConversion = !StringHelper.EqualsIgnoreCase(subtitleStream.Codec, profile.Format); + if (!profile.SupportsLanguage(subtitleStream.Language)) { continue; @@ -625,6 +670,11 @@ namespace MediaBrowser.Model.Dlna if (profile.Method == SubtitleDeliveryMethod.External && subtitleStream.IsTextSubtitleStream == MediaStream.IsTextFormat(profile.Format)) { + if (!requiresConversion) + { + return profile; + } + if (subtitleStream.SupportsExternalStream) { return profile; @@ -640,6 +690,8 @@ namespace MediaBrowser.Model.Dlna foreach (SubtitleProfile profile in subtitleProfiles) { + bool requiresConversion = !StringHelper.EqualsIgnoreCase(subtitleStream.Codec, profile.Format); + if (!profile.SupportsLanguage(subtitleStream.Language)) { continue; @@ -647,6 +699,11 @@ namespace MediaBrowser.Model.Dlna if (profile.Method == SubtitleDeliveryMethod.Embed && subtitleStream.IsTextSubtitleStream == MediaStream.IsTextFormat(profile.Format)) { + if (!requiresConversion) + { + return profile; + } + return profile; } } diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index 9bfa684be4..3977a9ab07 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -217,9 +217,9 @@ namespace MediaBrowser.Model.Dlna return list; } - public List GetExternalSubtitles(bool includeSelectedTrackOnly, string baseUrl, string accessToken) + public List GetExternalSubtitles(bool includeSelectedTrackOnly, bool enableAllProfiles, string baseUrl, string accessToken) { - List list = GetSubtitleProfiles(includeSelectedTrackOnly, baseUrl, accessToken); + List list = GetSubtitleProfiles(includeSelectedTrackOnly, enableAllProfiles, baseUrl, accessToken); List newList = new List(); // First add the selected track @@ -235,6 +235,11 @@ namespace MediaBrowser.Model.Dlna } public List GetSubtitleProfiles(bool includeSelectedTrackOnly, string baseUrl, string accessToken) + { + return GetSubtitleProfiles(includeSelectedTrackOnly, false, baseUrl, accessToken); + } + + public List GetSubtitleProfiles(bool includeSelectedTrackOnly, bool enableAllProfiles, string baseUrl, string accessToken) { List list = new List(); @@ -250,9 +255,7 @@ namespace MediaBrowser.Model.Dlna { if (stream.Type == MediaStreamType.Subtitle && stream.Index == SubtitleStreamIndex.Value) { - SubtitleStreamInfo info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks); - - list.Add(info); + AddSubtitleProfiles(list, stream, enableAllProfiles, baseUrl, accessToken, startPositionTicks); } } } @@ -263,9 +266,7 @@ namespace MediaBrowser.Model.Dlna { if (stream.Type == MediaStreamType.Subtitle && (!SubtitleStreamIndex.HasValue || stream.Index != SubtitleStreamIndex.Value)) { - SubtitleStreamInfo info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks); - - list.Add(info); + AddSubtitleProfiles(list, stream, enableAllProfiles, baseUrl, accessToken, startPositionTicks); } } } @@ -273,44 +274,57 @@ namespace MediaBrowser.Model.Dlna return list; } - private SubtitleStreamInfo GetSubtitleStreamInfo(MediaStream stream, string baseUrl, string accessToken, long startPositionTicks) + private void AddSubtitleProfiles(List list, MediaStream stream, bool enableAllProfiles, string baseUrl, string accessToken, long startPositionTicks) { - SubtitleStreamInfo info = GetSubtitleStreamInfo(stream); - - if (info.DeliveryMethod == SubtitleDeliveryMethod.External) + if (enableAllProfiles) { - if (MediaSource.Protocol == MediaProtocol.Http) - { - info.Url = stream.Path; - } - else if (!string.IsNullOrEmpty(baseUrl)) + foreach (SubtitleProfile profile in DeviceProfile.SubtitleProfiles) { - info.Url = string.Format("{0}/Videos/{1}/{2}/Subtitles/{3}/{4}/Stream.{5}", - baseUrl, - ItemId, - MediaSourceId, - StringHelper.ToStringCultureInvariant(stream.Index), - StringHelper.ToStringCultureInvariant(startPositionTicks), - SubtitleFormat); + SubtitleStreamInfo info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks, new[] { profile }); + + list.Add(info); } } + else + { + SubtitleStreamInfo info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks, DeviceProfile.SubtitleProfiles); - return info; + list.Add(info); + } } - private SubtitleStreamInfo GetSubtitleStreamInfo(MediaStream stream) + private SubtitleStreamInfo GetSubtitleStreamInfo(MediaStream stream, string baseUrl, string accessToken, long startPositionTicks, SubtitleProfile[] subtitleProfiles) { - SubtitleProfile subtitleProfile = StreamBuilder.GetSubtitleProfile(stream, DeviceProfile.SubtitleProfiles, Context); - - return new SubtitleStreamInfo + SubtitleProfile subtitleProfile = StreamBuilder.GetSubtitleProfile(stream, subtitleProfiles, Context); + SubtitleStreamInfo info = new SubtitleStreamInfo { IsForced = stream.IsForced, Language = stream.Language, Name = stream.Language ?? "Unknown", - Format = SubtitleFormat, + Format = subtitleProfile.Format, Index = stream.Index, DeliveryMethod = subtitleProfile.Method }; + + if (info.DeliveryMethod == SubtitleDeliveryMethod.External) + { + if (MediaSource.Protocol == MediaProtocol.File || !StringHelper.EqualsIgnoreCase(stream.Codec, subtitleProfile.Format)) + { + info.Url = string.Format("{0}/Videos/{1}/{2}/Subtitles/{3}/{4}/Stream.{5}", + baseUrl, + ItemId, + MediaSourceId, + StringHelper.ToStringCultureInvariant(stream.Index), + StringHelper.ToStringCultureInvariant(startPositionTicks), + subtitleProfile.Format); + } + else + { + info.Url = stream.Path; + } + } + + return info; } /// diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index 760829ebff..dfeed7450b 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -130,6 +130,12 @@ namespace MediaBrowser.Model.Entities /// The index. public int Index { get; set; } + /// + /// Gets or sets the score. + /// + /// The score. + public int? Score { get; set; } + /// /// Gets or sets a value indicating whether this instance is external. /// diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs index 2852f57195..0f092b5545 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs @@ -6,15 +6,14 @@ using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Subtitles; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Providers; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.MediaInfo { @@ -23,14 +22,16 @@ namespace MediaBrowser.Providers.MediaInfo private readonly ILibraryManager _libraryManager; private readonly IServerConfigurationManager _config; private readonly ISubtitleManager _subtitleManager; + private readonly IMediaSourceManager _mediaSourceManager; private readonly ILogger _logger; - public SubtitleScheduledTask(ILibraryManager libraryManager, IServerConfigurationManager config, ISubtitleManager subtitleManager, ILogger logger) + public SubtitleScheduledTask(ILibraryManager libraryManager, IServerConfigurationManager config, ISubtitleManager subtitleManager, ILogger logger, IMediaSourceManager mediaSourceManager) { _libraryManager = libraryManager; _config = config; _subtitleManager = subtitleManager; _logger = logger; + _mediaSourceManager = mediaSourceManager; } public string Name @@ -107,7 +108,7 @@ namespace MediaBrowser.Providers.MediaInfo (options.DownloadMovieSubtitles && video is Movie)) { - var mediaStreams = video.GetMediaSources(false).First().MediaStreams; + var mediaStreams = _mediaSourceManager.GetStaticMediaSources(video, false).First().MediaStreams; var downloadedLanguages = await new SubtitleDownloader(_logger, _subtitleManager) diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index 756ff69178..dc6f4a5250 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -261,7 +261,7 @@ namespace MediaBrowser.Server.Implementations.Dto { if (user == null) { - dto.MediaSources = hasMediaSources.GetMediaSources(true).ToList(); + dto.MediaSources = _mediaSourceManager().GetStaticMediaSources(hasMediaSources, true).ToList(); } else { @@ -269,7 +269,7 @@ namespace MediaBrowser.Server.Implementations.Dto } } } - + if (fields.Contains(ItemFields.Studios)) { AttachStudios(dto, item); @@ -1280,7 +1280,7 @@ namespace MediaBrowser.Server.Implementations.Dto } else { - mediaStreams = iHasMediaSources.GetMediaSources(true).First().MediaStreams; + mediaStreams = _mediaSourceManager().GetStaticMediaSources(iHasMediaSources, true).First().MediaStreams; } dto.MediaStreams = mediaStreams; @@ -1453,7 +1453,7 @@ namespace MediaBrowser.Server.Implementations.Dto var tvChannel = item as LiveTvChannel; if (tvChannel != null) { - dto.MediaSources = tvChannel.GetMediaSources(true).ToList(); + dto.MediaSources = _mediaSourceManager().GetStaticMediaSources(tvChannel, true).ToList(); } var channelItem = item as IChannelItem; diff --git a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs index e832142a90..66eeb61f72 100644 --- a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs +++ b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs @@ -135,6 +135,7 @@ namespace MediaBrowser.Server.Implementations.Library IEnumerable mediaSources; var hasMediaSources = (IHasMediaSources)item; + User user = null; if (string.IsNullOrWhiteSpace(userId)) { @@ -142,7 +143,7 @@ namespace MediaBrowser.Server.Implementations.Library } else { - var user = _userManager.GetUserById(userId); + user = _userManager.GetUserById(userId); mediaSources = GetStaticMediaSources(hasMediaSources, enablePathSubstitution, user); } @@ -154,6 +155,10 @@ namespace MediaBrowser.Server.Implementations.Library foreach (var source in dynamicMediaSources) { + if (user != null) + { + SetUserProperties(source, user); + } if (source.Protocol == MediaProtocol.File) { source.SupportsDirectStream = File.Exists(source.Path); @@ -225,6 +230,11 @@ namespace MediaBrowser.Server.Implementations.Library return GetPlayackMediaSources(id, null, enablePathSubstitution, cancellationToken); } + public MediaSourceInfo GetStaticMediaSource(IHasMediaSources item, string mediaSourceId, bool enablePathSubstitution) + { + return GetStaticMediaSources(item, enablePathSubstitution).FirstOrDefault(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase)); + } + public IEnumerable GetStaticMediaSources(IHasMediaSources item, bool enablePathSubstitution) { if (item == null) @@ -288,6 +298,9 @@ namespace MediaBrowser.Server.Implementations.Library preferredSubs, user.Configuration.SubtitleMode, audioLangage); + + MediaStreamSelector.SetSubtitleStreamScores(source.MediaStreams, preferredSubs, + user.Configuration.SubtitleMode, audioLangage); } private IEnumerable SortMediaSources(IEnumerable sources) @@ -311,11 +324,6 @@ namespace MediaBrowser.Server.Implementations.Library .ToList(); } - public MediaSourceInfo GetStaticMediaSource(IHasMediaSources item, string mediaSourceId, bool enablePathSubstitution) - { - return GetStaticMediaSources(item, enablePathSubstitution).FirstOrDefault(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase)); - } - private readonly ConcurrentDictionary _openStreams = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1); @@ -428,9 +436,16 @@ namespace MediaBrowser.Server.Implementations.Library try { - var tuple = GetProvider(id); + LiveStreamInfo current; + if (_openStreams.TryGetValue(id, out current)) + { + if (current.MediaSource.RequiresClosing) + { + var tuple = GetProvider(id); - await tuple.Item1.CloseMediaSource(tuple.Item2, cancellationToken).ConfigureAwait(false); + await tuple.Item1.CloseMediaSource(tuple.Item2, cancellationToken).ConfigureAwait(false); + } + } LiveStreamInfo removed; if (_openStreams.TryRemove(id, out removed)) diff --git a/MediaBrowser.Server.Implementations/Library/UserViewManager.cs b/MediaBrowser.Server.Implementations/Library/UserViewManager.cs index e63a275511..757f085623 100644 --- a/MediaBrowser.Server.Implementations/Library/UserViewManager.cs +++ b/MediaBrowser.Server.Implementations/Library/UserViewManager.cs @@ -215,7 +215,7 @@ namespace MediaBrowser.Server.Implementations.Library if (request.IsPlayed.HasValue) { var val = request.IsPlayed.Value; - if (i.IsPlayed(currentUser) != val) + if (i is Video && i.IsPlayed(currentUser) != val) { return false; } diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs index 5de4cf4998..d549cad461 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs @@ -17,11 +17,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv private readonly ILiveTvManager _liveTvManager; private readonly IJsonSerializer _jsonSerializer; private readonly ILogger _logger; + private readonly IMediaSourceManager _mediaSourceManager; - public LiveTvMediaSourceProvider(ILiveTvManager liveTvManager, IJsonSerializer jsonSerializer, ILogManager logManager) + public LiveTvMediaSourceProvider(ILiveTvManager liveTvManager, IJsonSerializer jsonSerializer, ILogManager logManager, IMediaSourceManager mediaSourceManager) { _liveTvManager = liveTvManager; _jsonSerializer = jsonSerializer; + _mediaSourceManager = mediaSourceManager; _logger = logManager.GetLogger(GetType().Name); } @@ -63,7 +65,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv { var hasMediaSources = (IHasMediaSources)item; - sources = hasMediaSources.GetMediaSources(false) + sources = _mediaSourceManager.GetStaticMediaSources(hasMediaSources, false) .ToList(); } diff --git a/MediaBrowser.Server.Implementations/Localization/Server/server.json b/MediaBrowser.Server.Implementations/Localization/Server/server.json index f52d8a9fe3..0cff99c5d1 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/server.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/server.json @@ -52,6 +52,7 @@ "HeaderAddUser": "Add User", "LabelAddConnectSupporterHelp": "To add a user who isn't listed, you'll need to first link their account to Emby Connect from their user profile page.", "LabelPinCode": "Pin code:", + "OptionHideWatchedContentFromLatestMedia": "Hide watched content from latest media", "ButtonOk": "Ok", "ButtonCancel": "Cancel", "ButtonExit": "Exit", diff --git a/MediaBrowser.Server.Implementations/Sync/CloudSyncProfile.cs b/MediaBrowser.Server.Implementations/Sync/CloudSyncProfile.cs index 43fb10df00..73400f8345 100644 --- a/MediaBrowser.Server.Implementations/Sync/CloudSyncProfile.cs +++ b/MediaBrowser.Server.Implementations/Sync/CloudSyncProfile.cs @@ -198,8 +198,7 @@ namespace MediaBrowser.Server.Implementations.Sync var maxAudioChannels = supportsAc3 || supportsDca ? "5" : "2"; codecProfiles.Add(new CodecProfile { - Type = CodecType.Audio, - Codec = "mpeg4", + Type = CodecType.VideoAudio, Conditions = new[] { new ProfileCondition @@ -207,7 +206,7 @@ namespace MediaBrowser.Server.Implementations.Sync Condition = ProfileConditionType.LessThanEqual, Property = ProfileConditionValue.AudioChannels, Value = maxAudioChannels, - IsRequired = false + IsRequired = true }, new ProfileCondition { diff --git a/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs b/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs index 7eb015ae7c..271b2bb397 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs @@ -495,7 +495,7 @@ namespace MediaBrowser.Server.Implementations.Sync // No sense creating external subs if we're already burning one into the video var externalSubs = streamInfo.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode ? new List() : - streamInfo.GetExternalSubtitles(false, null, null); + streamInfo.GetExternalSubtitles(false, true, null, null); // Mark as requiring conversion if transcoding the video, or if any subtitles need to be extracted var requiresVideoTranscoding = streamInfo.PlayMethod == PlayMethod.Transcode && jobOptions.IsConverting; @@ -823,7 +823,7 @@ namespace MediaBrowser.Server.Implementations.Sync var hasMediaSources = item as IHasMediaSources; - var mediaSources = hasMediaSources.GetMediaSources(false).ToList(); + var mediaSources = _mediaSourceManager.GetStaticMediaSources(hasMediaSources, false).ToList(); var preferredAudio = string.IsNullOrEmpty(user.Configuration.AudioLanguagePreference) ? new string[] { } diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index aad88d022f..df7b3f061e 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -530,7 +530,7 @@ namespace MediaBrowser.Server.Startup.Common RegisterSingleInstance(new SessionContext(UserManager, authContext, SessionManager)); RegisterSingleInstance(new AuthService(UserManager, authContext, ServerConfigurationManager, ConnectManager, SessionManager, DeviceManager)); - SubtitleEncoder = new SubtitleEncoder(LibraryManager, LogManager.GetLogger("SubtitleEncoder"), ApplicationPaths, FileSystemManager, MediaEncoder, JsonSerializer); + SubtitleEncoder = new SubtitleEncoder(LibraryManager, LogManager.GetLogger("SubtitleEncoder"), ApplicationPaths, FileSystemManager, MediaEncoder, JsonSerializer, HttpClient, MediaSourceManager); RegisterSingleInstance(SubtitleEncoder); await ConfigureDisplayPreferencesRepositories().ConfigureAwait(false); From 5965afecde2536c0a2ec566fd6afdb21add8aa50 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 31 Mar 2015 14:50:08 -0400 Subject: [PATCH 30/52] live tv fix --- MediaBrowser.Api/Playback/MediaInfoService.cs | 23 ----- .../Playback/TranscodingThrottler.cs | 2 +- .../HttpClientManager/HttpClientManager.cs | 32 ++++--- .../LiveTv/LiveTvProgram.cs | 6 ++ .../Sync/ISyncDataProvider.cs | 16 ++++ MediaBrowser.Model/ApiClient/IApiClient.cs | 5 +- .../MediaInfo/PlaybackInfoRequest.cs | 16 ++++ MediaBrowser.Model/Sync/LocalItem.cs | 5 + MediaBrowser.Model/Sync/SyncDataRequest.cs | 1 + .../Channels/ChannelImageProvider.cs | 1 - .../Library/MediaSourceManager.cs | 10 +- .../LiveTv/LiveTvDtoService.cs | 2 +- .../LiveTv/LiveTvManager.cs | 6 +- .../Sync/MediaSync.cs | 11 ++- .../Sync/SyncManager.cs | 95 ++++++++++++++++++- .../Sync/TargetDataProvider.cs | 30 +++++- Nuget/MediaBrowser.Common.Internal.nuspec | 4 +- Nuget/MediaBrowser.Common.nuspec | 2 +- Nuget/MediaBrowser.Model.Signed.nuspec | 2 +- Nuget/MediaBrowser.Server.Core.nuspec | 4 +- 20 files changed, 207 insertions(+), 66 deletions(-) diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs index 0930c00024..ca735f0684 100644 --- a/MediaBrowser.Api/Playback/MediaInfoService.cs +++ b/MediaBrowser.Api/Playback/MediaInfoService.cs @@ -39,29 +39,6 @@ namespace MediaBrowser.Api.Playback [Route("/Items/{Id}/PlaybackInfo", "POST", Summary = "Gets live playback media info for an item")] public class GetPostedPlaybackInfo : PlaybackInfoRequest, IReturn { - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string UserId { get; set; } - - [ApiMember(Name = "MaxStreamingBitrate", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")] - public int? MaxStreamingBitrate { get; set; } - - [ApiMember(Name = "StartTimeTicks", Description = "Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")] - public long? StartTimeTicks { get; set; } - - [ApiMember(Name = "AudioStreamIndex", Description = "Optional. The index of the audio stream to use. If omitted the first audio stream will be used.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")] - public int? AudioStreamIndex { get; set; } - - [ApiMember(Name = "SubtitleStreamIndex", Description = "Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")] - public int? SubtitleStreamIndex { get; set; } - - [ApiMember(Name = "MediaSourceId", Description = "The media version id, if playing an alternate version", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string MediaSourceId { get; set; } - - [ApiMember(Name = "LiveStreamId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string LiveStreamId { get; set; } } [Route("/LiveStreams/Open", "POST", Summary = "Opens a media source")] diff --git a/MediaBrowser.Api/Playback/TranscodingThrottler.cs b/MediaBrowser.Api/Playback/TranscodingThrottler.cs index ff79bb48f4..58cfa086e3 100644 --- a/MediaBrowser.Api/Playback/TranscodingThrottler.cs +++ b/MediaBrowser.Api/Playback/TranscodingThrottler.cs @@ -42,7 +42,7 @@ namespace MediaBrowser.Api.Playback var options = GetOptions(); - if (/*options.EnableThrottling &&*/ IsThrottleAllowed(_job, options.ThrottleThresholdSeconds)) + if (options.EnableThrottling && IsThrottleAllowed(_job, options.ThrottleThresholdSeconds)) { PauseTranscoding(); } diff --git a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs index b925649fce..94c91c55aa 100644 --- a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs +++ b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs @@ -107,30 +107,40 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager private WebRequest GetRequest(HttpRequestOptions options, string method, bool enableHttpCompression) { - var request = (HttpWebRequest)WebRequest.Create(options.Url); + var request = WebRequest.Create(options.Url); + var httpWebRequest = request as HttpWebRequest; - AddRequestHeaders(request, options); + if (httpWebRequest != null) + { + AddRequestHeaders(httpWebRequest, options); - request.AutomaticDecompression = enableHttpCompression ? DecompressionMethods.Deflate : DecompressionMethods.None; + httpWebRequest.AutomaticDecompression = enableHttpCompression ? DecompressionMethods.Deflate : DecompressionMethods.None; + } request.CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache); - if (options.EnableKeepAlive) + if (httpWebRequest != null) { - request.KeepAlive = true; + if (options.EnableKeepAlive) + { + httpWebRequest.KeepAlive = true; + } } request.Method = method; request.Timeout = options.TimeoutMs; - if (!string.IsNullOrEmpty(options.Host)) + if (httpWebRequest != null) { - request.Host = options.Host; - } + if (!string.IsNullOrEmpty(options.Host)) + { + httpWebRequest.Host = options.Host; + } - if (!string.IsNullOrEmpty(options.Referer)) - { - request.Referer = options.Referer; + if (!string.IsNullOrEmpty(options.Referer)) + { + httpWebRequest.Referer = options.Referer; + } } //request.ServicePoint.BindIPEndPointDelegate = BindIPEndPointCallback; diff --git a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs index 0b07d8b6d1..0609df4c6b 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs @@ -34,6 +34,12 @@ namespace MediaBrowser.Controller.LiveTv /// The channel identifier. public string ExternalChannelId { get; set; } + /// + /// Gets or sets the original air date. + /// + /// The original air date. + public DateTime? OriginalAirDate { get; set; } + /// /// Gets or sets the type of the channel. /// diff --git a/MediaBrowser.Controller/Sync/ISyncDataProvider.cs b/MediaBrowser.Controller/Sync/ISyncDataProvider.cs index f84748b971..04101fd465 100644 --- a/MediaBrowser.Controller/Sync/ISyncDataProvider.cs +++ b/MediaBrowser.Controller/Sync/ISyncDataProvider.cs @@ -14,6 +14,14 @@ namespace MediaBrowser.Controller.Sync /// Task<List<System.String>>. Task> GetServerItemIds(SyncTarget target, string serverId); + /// + /// Gets the synchronize job item ids. + /// + /// The target. + /// The server identifier. + /// Task<List<System.String>>. + Task> GetSyncJobItemIds(SyncTarget target, string serverId); + /// /// Adds the or update. /// @@ -46,5 +54,13 @@ namespace MediaBrowser.Controller.Sync /// The item identifier. /// Task<LocalItem>. Task> GetCachedItems(SyncTarget target, string serverId, string itemId); + /// + /// Gets the cached items by synchronize job item identifier. + /// + /// The target. + /// The server identifier. + /// The synchronize job item identifier. + /// Task<List<LocalItem>>. + Task> GetCachedItemsBySyncJobItemId(SyncTarget target, string serverId, string syncJobItemId); } } diff --git a/MediaBrowser.Model/ApiClient/IApiClient.cs b/MediaBrowser.Model/ApiClient/IApiClient.cs index 58af016150..9675de38b0 100644 --- a/MediaBrowser.Model/ApiClient/IApiClient.cs +++ b/MediaBrowser.Model/ApiClient/IApiClient.cs @@ -248,10 +248,9 @@ namespace MediaBrowser.Model.ApiClient /// /// Gets the playback information. /// - /// The item identifier. - /// The user identifier. + /// The request. /// Task<LiveMediaInfoResult>. - Task GetPlaybackInfo(string itemId, string userId); + Task GetPlaybackInfo(PlaybackInfoRequest request); /// /// Gets the users async. diff --git a/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs b/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs index ffd4995ad9..124739073a 100644 --- a/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs +++ b/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs @@ -4,6 +4,22 @@ namespace MediaBrowser.Model.MediaInfo { public class PlaybackInfoRequest { + public string Id { get; set; } + + public string UserId { get; set; } + + public int? MaxStreamingBitrate { get; set; } + + public long? StartTimeTicks { get; set; } + + public int? AudioStreamIndex { get; set; } + + public int? SubtitleStreamIndex { get; set; } + + public string MediaSourceId { get; set; } + + public string LiveStreamId { get; set; } + public DeviceProfile DeviceProfile { get; set; } } } diff --git a/MediaBrowser.Model/Sync/LocalItem.cs b/MediaBrowser.Model/Sync/LocalItem.cs index 37f605a590..dbbecaf057 100644 --- a/MediaBrowser.Model/Sync/LocalItem.cs +++ b/MediaBrowser.Model/Sync/LocalItem.cs @@ -31,6 +31,11 @@ namespace MediaBrowser.Model.Sync /// The item identifier. public string ItemId { get; set; } /// + /// Gets or sets the synchronize job item identifier. + /// + /// The synchronize job item identifier. + public string SyncJobItemId { get; set; } + /// /// Gets or sets the user ids with access. /// /// The user ids with access. diff --git a/MediaBrowser.Model/Sync/SyncDataRequest.cs b/MediaBrowser.Model/Sync/SyncDataRequest.cs index dc33239a04..0df4de86d1 100644 --- a/MediaBrowser.Model/Sync/SyncDataRequest.cs +++ b/MediaBrowser.Model/Sync/SyncDataRequest.cs @@ -6,6 +6,7 @@ namespace MediaBrowser.Model.Sync { public List LocalItemIds { get; set; } public List OfflineUserIds { get; set; } + public List SyncJobItemIds { get; set; } public string TargetId { get; set; } diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelImageProvider.cs b/MediaBrowser.Server.Implementations/Channels/ChannelImageProvider.cs index 5c033e6bdc..f13c71c6df 100644 --- a/MediaBrowser.Server.Implementations/Channels/ChannelImageProvider.cs +++ b/MediaBrowser.Server.Implementations/Channels/ChannelImageProvider.cs @@ -2,7 +2,6 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; -using System; using System.Collections.Generic; using System.Linq; using System.Threading; diff --git a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs index 66eeb61f72..4fab95263f 100644 --- a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs +++ b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs @@ -129,6 +129,11 @@ namespace MediaBrowser.Server.Implementations.Library return list; } + public Task> GetPlayackMediaSources(string id, bool enablePathSubstitution, CancellationToken cancellationToken) + { + return GetPlayackMediaSources(id, null, enablePathSubstitution, cancellationToken); + } + public async Task> GetPlayackMediaSources(string id, string userId, bool enablePathSubstitution, CancellationToken cancellationToken) { var item = _libraryManager.GetItemById(id); @@ -225,11 +230,6 @@ namespace MediaBrowser.Server.Implementations.Library } } - public Task> GetPlayackMediaSources(string id, bool enablePathSubstitution, CancellationToken cancellationToken) - { - return GetPlayackMediaSources(id, null, enablePathSubstitution, cancellationToken); - } - public MediaSourceInfo GetStaticMediaSource(IHasMediaSources item, string mediaSourceId, bool enablePathSubstitution) { return GetStaticMediaSources(item, enablePathSubstitution).FirstOrDefault(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase)); diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs index 7f4440fbce..401cf87657 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs @@ -373,7 +373,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv StartDate = item.StartDate, OfficialRating = item.OfficialRating, IsHD = item.IsHD, - OriginalAirDate = item.PremiereDate, + OriginalAirDate = item.OriginalAirDate, Audio = item.Audio, CommunityRating = GetClientCommunityRating(item.CommunityRating), IsRepeat = item.IsRepeat, diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index f676918593..a39781d6a3 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -589,13 +589,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv item.Name = info.Name; item.OfficialRating = info.OfficialRating; item.Overview = info.Overview; - item.PremiereDate = info.OriginalAirDate; + item.OriginalAirDate = info.OriginalAirDate; item.ProviderImagePath = info.ImagePath; item.ProviderImageUrl = info.ImageUrl; item.RunTimeTicks = (info.EndDate - info.StartDate).Ticks; item.StartDate = info.StartDate; - item.ProductionYear = info.ProductionYear; + item.ProductionYear = info.ProductionYear; + item.PremiereDate = item.PremiereDate ?? info.OriginalAirDate; + await item.UpdateToRepository(ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false); return item; diff --git a/MediaBrowser.Server.Implementations/Sync/MediaSync.cs b/MediaBrowser.Server.Implementations/Sync/MediaSync.cs index e7333939e3..5bc8b80887 100644 --- a/MediaBrowser.Server.Implementations/Sync/MediaSync.cs +++ b/MediaBrowser.Server.Implementations/Sync/MediaSync.cs @@ -67,12 +67,12 @@ namespace MediaBrowser.Server.Implementations.Sync SyncTarget target, CancellationToken cancellationToken) { - var localIds = await dataProvider.GetServerItemIds(target, serverId).ConfigureAwait(false); + var jobItemIds = await dataProvider.GetSyncJobItemIds(target, serverId).ConfigureAwait(false); var result = await _syncManager.SyncData(new SyncDataRequest { TargetId = target.Id, - LocalItemIds = localIds + SyncJobItemIds = jobItemIds }).ConfigureAwait(false); @@ -285,11 +285,11 @@ namespace MediaBrowser.Server.Implementations.Sync private async Task RemoveItem(IServerSyncProvider provider, ISyncDataProvider dataProvider, string serverId, - string itemId, + string syncJobItemId, SyncTarget target, CancellationToken cancellationToken) { - var localItems = await dataProvider.GetCachedItems(target, serverId, itemId); + var localItems = await dataProvider.GetCachedItemsBySyncJobItemId(target, serverId, syncJobItemId); foreach (var localItem in localItems) { @@ -350,7 +350,8 @@ namespace MediaBrowser.Server.Implementations.Sync ItemId = libraryItem.Id, ServerId = serverId, LocalPath = localPath, - Id = GetLocalId(syncedItem.SyncJobItemId, libraryItem.Id) + Id = GetLocalId(syncedItem.SyncJobItemId, libraryItem.Id), + SyncJobItemId = syncedItem.SyncJobItemId }; } diff --git a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs index 3acc79088d..102218979c 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs @@ -3,6 +3,7 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Events; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; +using MediaBrowser.Controller; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; @@ -42,7 +43,7 @@ namespace MediaBrowser.Server.Implementations.Sync private readonly ILogger _logger; private readonly IUserManager _userManager; private readonly Func _dtoService; - private readonly IApplicationHost _appHost; + private readonly IServerApplicationHost _appHost; private readonly ITVSeriesManager _tvSeriesManager; private readonly Func _mediaEncoder; private readonly IFileSystem _fileSystem; @@ -60,7 +61,7 @@ namespace MediaBrowser.Server.Implementations.Sync public event EventHandler> SyncJobItemUpdated; public event EventHandler> SyncJobItemCreated; - public SyncManager(ILibraryManager libraryManager, ISyncRepository repo, IImageProcessor imageProcessor, ILogger logger, IUserManager userManager, Func dtoService, IApplicationHost appHost, ITVSeriesManager tvSeriesManager, Func mediaEncoder, IFileSystem fileSystem, Func subtitleEncoder, IConfigurationManager config, IUserDataManager userDataManager, Func mediaSourceManager, IJsonSerializer json) + public SyncManager(ILibraryManager libraryManager, ISyncRepository repo, IImageProcessor imageProcessor, ILogger logger, IUserManager userManager, Func dtoService, IServerApplicationHost appHost, ITVSeriesManager tvSeriesManager, Func mediaEncoder, IFileSystem fileSystem, Func subtitleEncoder, IConfigurationManager config, IUserDataManager userDataManager, Func mediaSourceManager, IJsonSerializer json) { _libraryManager = libraryManager; _repo = repo; @@ -94,7 +95,7 @@ namespace MediaBrowser.Server.Implementations.Sync public ISyncDataProvider GetDataProvider(IServerSyncProvider provider, SyncTarget target) { - return _dataProviders.GetOrAdd(target.Id, key => new TargetDataProvider(provider, target, _appHost.SystemId, _logger, _json, _fileSystem, _config.CommonApplicationPaths)); + return _dataProviders.GetOrAdd(target.Id, key => new TargetDataProvider(provider, target, _appHost, _logger, _json, _fileSystem, _config.CommonApplicationPaths)); } public async Task CreateJob(SyncJobRequest request) @@ -737,10 +738,15 @@ namespace MediaBrowser.Server.Implementations.Sync public async Task SyncData(SyncDataRequest request) { + if (request.SyncJobItemIds != null) + { + return await SyncDataUsingSyncJobItemIds(request).ConfigureAwait(false); + } + var jobItemResult = GetJobItems(new SyncJobItemQuery { TargetId = request.TargetId, - Statuses = new SyncJobItemStatus[] { SyncJobItemStatus.Synced } + Statuses = new[] { SyncJobItemStatus.Synced } }); var response = new SyncDataResponse(); @@ -816,6 +822,87 @@ namespace MediaBrowser.Server.Implementations.Sync return response; } + private async Task SyncDataUsingSyncJobItemIds(SyncDataRequest request) + { + var jobItemResult = GetJobItems(new SyncJobItemQuery + { + TargetId = request.TargetId, + Statuses = new[] { SyncJobItemStatus.Synced } + }); + + var response = new SyncDataResponse(); + + foreach (var jobItem in jobItemResult.Items) + { + if (request.SyncJobItemIds.Contains(jobItem.Id, StringComparer.OrdinalIgnoreCase)) + { + var job = _repo.GetJob(jobItem.JobId); + var user = _userManager.GetUserById(job.UserId); + + if (jobItem.IsMarkedForRemoval) + { + // Tell the device to remove it since it has been marked for removal + response.ItemIdsToRemove.Add(jobItem.Id); + } + else if (user == null) + { + // Tell the device to remove it since the user is gone now + response.ItemIdsToRemove.Add(jobItem.Id); + } + else if (job.UnwatchedOnly) + { + var libraryItem = _libraryManager.GetItemById(jobItem.ItemId); + + if (IsLibraryItemAvailable(libraryItem)) + { + if (libraryItem.IsPlayed(user) && libraryItem is Video) + { + // Tell the device to remove it since it has been played + response.ItemIdsToRemove.Add(jobItem.Id); + } + } + else + { + // Tell the device to remove it since it's no longer available + response.ItemIdsToRemove.Add(jobItem.Id); + } + } + } + else + { + // Content is no longer on the device + jobItem.Status = SyncJobItemStatus.RemovedFromDevice; + await UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); + } + } + + // Now check each item that's on the device + foreach (var syncJobItemId in request.SyncJobItemIds) + { + // See if it's already marked for removal + if (response.ItemIdsToRemove.Contains(syncJobItemId, StringComparer.OrdinalIgnoreCase)) + { + continue; + } + + // If there isn't a sync job for this item, mark it for removal + if (!jobItemResult.Items.Any(i => string.Equals(syncJobItemId, i.Id, StringComparison.OrdinalIgnoreCase))) + { + response.ItemIdsToRemove.Add(syncJobItemId); + } + } + + response.ItemIdsToRemove = response.ItemIdsToRemove.Distinct(StringComparer.OrdinalIgnoreCase).ToList(); + + var itemsOnDevice = request.LocalItemIds + .Except(response.ItemIdsToRemove) + .ToList(); + + SetUserAccess(request, response, itemsOnDevice); + + return response; + } + private void SetUserAccess(SyncDataRequest request, SyncDataResponse response, List itemIds) { var users = request.OfflineUserIds diff --git a/MediaBrowser.Server.Implementations/Sync/TargetDataProvider.cs b/MediaBrowser.Server.Implementations/Sync/TargetDataProvider.cs index ca9d96c12f..dea8688482 100644 --- a/MediaBrowser.Server.Implementations/Sync/TargetDataProvider.cs +++ b/MediaBrowser.Server.Implementations/Sync/TargetDataProvider.cs @@ -1,6 +1,7 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; +using MediaBrowser.Controller; using MediaBrowser.Controller.Sync; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; @@ -26,11 +27,11 @@ namespace MediaBrowser.Server.Implementations.Sync private readonly IJsonSerializer _json; private readonly IFileSystem _fileSystem; private readonly IApplicationPaths _appPaths; - private readonly string _serverId; + private readonly IServerApplicationHost _appHost; private readonly SemaphoreSlim _cacheFileLock = new SemaphoreSlim(1, 1); - public TargetDataProvider(IServerSyncProvider provider, SyncTarget target, string serverId, ILogger logger, IJsonSerializer json, IFileSystem fileSystem, IApplicationPaths appPaths) + public TargetDataProvider(IServerSyncProvider provider, SyncTarget target, IServerApplicationHost appHost, ILogger logger, IJsonSerializer json, IFileSystem fileSystem, IApplicationPaths appPaths) { _logger = logger; _json = json; @@ -38,7 +39,7 @@ namespace MediaBrowser.Server.Implementations.Sync _target = target; _fileSystem = fileSystem; _appPaths = appPaths; - _serverId = serverId; + _appHost = appHost; } private string GetCachePath() @@ -50,13 +51,21 @@ namespace MediaBrowser.Server.Implementations.Sync { var parts = new List { - _serverId, + _appHost.FriendlyName, "data.json" }; + parts = parts.Select(i => GetValidFilename(_provider, i)).ToList(); + return _provider.GetFullPath(parts, _target); } + private string GetValidFilename(IServerSyncProvider provider, string filename) + { + // We can always add this method to the sync provider if it's really needed + return _fileSystem.GetValidFilename(filename); + } + private async Task CacheData(Stream stream) { var cachePath = GetCachePath(); @@ -167,6 +176,11 @@ namespace MediaBrowser.Server.Implementations.Sync return GetData(items => items.Where(i => string.Equals(i.ServerId, serverId, StringComparison.OrdinalIgnoreCase)).Select(i => i.ItemId).ToList()); } + public Task> GetSyncJobItemIds(SyncTarget target, string serverId) + { + return GetData(items => items.Where(i => string.Equals(i.ServerId, serverId, StringComparison.OrdinalIgnoreCase)).Select(i => i.SyncJobItemId).Where(i => !string.IsNullOrWhiteSpace(i)).ToList()); + } + public Task AddOrUpdate(SyncTarget target, LocalItem item) { return UpdateData(items => @@ -239,5 +253,13 @@ namespace MediaBrowser.Server.Implementations.Sync return items.Where(i => string.Equals(i.ServerId, serverId, StringComparison.OrdinalIgnoreCase) && string.Equals(i.ItemId, itemId, StringComparison.OrdinalIgnoreCase)) .ToList(); } + + public async Task> GetCachedItemsBySyncJobItemId(SyncTarget target, string serverId, string syncJobItemId) + { + var items = await GetCachedData().ConfigureAwait(false); + + return items.Where(i => string.Equals(i.ServerId, serverId, StringComparison.OrdinalIgnoreCase) && string.Equals(i.SyncJobItemId, syncJobItemId, StringComparison.OrdinalIgnoreCase)) + .ToList(); + } } } diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index 40da3899c5..c5acc0a42d 100644 --- a/Nuget/MediaBrowser.Common.Internal.nuspec +++ b/Nuget/MediaBrowser.Common.Internal.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common.Internal - 3.0.609 + 3.0.611 MediaBrowser.Common.Internal Luke ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains common components shared by Emby Theater and Emby Server. Not intended for plugin developer consumption. Copyright © Emby 2013 - + diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index 7aa9d30551..1b29b02729 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common - 3.0.609 + 3.0.611 MediaBrowser.Common Emby Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Model.Signed.nuspec b/Nuget/MediaBrowser.Model.Signed.nuspec index 8d65ec4315..6ee79dd8c9 100644 --- a/Nuget/MediaBrowser.Model.Signed.nuspec +++ b/Nuget/MediaBrowser.Model.Signed.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Model.Signed - 3.0.609 + 3.0.611 MediaBrowser.Model - Signed Edition Emby Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index 9a5264da95..c8b062f3f3 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Server.Core - 3.0.609 + 3.0.611 Media Browser.Server.Core Emby Team ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains core components required to build plugins for Emby Server. Copyright © Emby 2013 - + From 2632093ebbc95f8963b8d3adbd973dda61854496 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 31 Mar 2015 14:52:41 -0400 Subject: [PATCH 31/52] updated nuget --- Nuget/MediaBrowser.Common.Internal.nuspec | 4 ++-- Nuget/MediaBrowser.Common.nuspec | 2 +- Nuget/MediaBrowser.Model.Signed.nuspec | 2 +- Nuget/MediaBrowser.Server.Core.nuspec | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index c5acc0a42d..d310716661 100644 --- a/Nuget/MediaBrowser.Common.Internal.nuspec +++ b/Nuget/MediaBrowser.Common.Internal.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common.Internal - 3.0.611 + 3.0.612 MediaBrowser.Common.Internal Luke ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains common components shared by Emby Theater and Emby Server. Not intended for plugin developer consumption. Copyright © Emby 2013 - + diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index 1b29b02729..d4d7de019a 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common - 3.0.611 + 3.0.612 MediaBrowser.Common Emby Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Model.Signed.nuspec b/Nuget/MediaBrowser.Model.Signed.nuspec index 6ee79dd8c9..de9c503b92 100644 --- a/Nuget/MediaBrowser.Model.Signed.nuspec +++ b/Nuget/MediaBrowser.Model.Signed.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Model.Signed - 3.0.611 + 3.0.612 MediaBrowser.Model - Signed Edition Emby Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index c8b062f3f3..b0008f9d03 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Server.Core - 3.0.611 + 3.0.612 Media Browser.Server.Core Emby Team ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains core components required to build plugins for Emby Server. Copyright © Emby 2013 - + From 0bcc43098ea5d58b7ae346e9f9599547d64259c2 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 31 Mar 2015 15:33:38 -0400 Subject: [PATCH 32/52] fix handling of bare nfo's --- MediaBrowser.Model/Dlna/StreamBuilder.cs | 6 +++++- MediaBrowser.Model/Dto/MediaSourceInfo.cs | 7 ++++++- MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs | 14 ++++++++++++++ MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs | 14 +++++++++----- 4 files changed, 34 insertions(+), 7 deletions(-) diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index b27138b4a7..bc9f07d04a 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -341,7 +341,11 @@ namespace MediaBrowser.Model.Dlna MediaStream subtitleStream = playlistItem.SubtitleStreamIndex.HasValue ? item.GetMediaStream(MediaStreamType.Subtitle, playlistItem.SubtitleStreamIndex.Value) : null; MediaStream audioStream = item.GetDefaultAudioStream(options.AudioStreamIndex ?? item.DefaultAudioStreamIndex); - int? audioStreamIndex = audioStream == null ? (int?)null : audioStream.Index; + int? audioStreamIndex = null; + if (audioStream != null) + { + audioStreamIndex = audioStream.Index; + } MediaStream videoStream = item.VideoStream; diff --git a/MediaBrowser.Model/Dto/MediaSourceInfo.cs b/MediaBrowser.Model/Dto/MediaSourceInfo.cs index 302d18bc0f..8897edcbdc 100644 --- a/MediaBrowser.Model/Dto/MediaSourceInfo.cs +++ b/MediaBrowser.Model/Dto/MediaSourceInfo.cs @@ -150,7 +150,12 @@ namespace MediaBrowser.Model.Dto } } - return numStreams == 0 ? (int?)null : numMatches; + if (numStreams == 0) + { + return null; + } + + return numMatches; } public bool? IsSecondaryAudio(MediaStream stream) diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs index 682175f793..c854f9f4a2 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs @@ -138,6 +138,20 @@ namespace MediaBrowser.XbmcMetadata.Parsers xml = xml.Substring(0, index + 1); } + else + { + // If the file is just an Imdb url, handle that + + var imdbId = xml.Split('/') + .FirstOrDefault(i => i.StartsWith("tt", StringComparison.OrdinalIgnoreCase)); + + if (!string.IsNullOrWhiteSpace(imdbId)) + { + item.SetProviderId(MetadataProviders.Imdb, imdbId); + } + + return; + } using (var ms = new MemoryStream()) { diff --git a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs index 3224f36bc6..5c4319d228 100644 --- a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs @@ -153,7 +153,7 @@ namespace MediaBrowser.XbmcMetadata.Savers { return GetLocalSavePath(item); } - + /// /// Gets the save path. /// @@ -184,7 +184,7 @@ namespace MediaBrowser.XbmcMetadata.Savers public void Save(IHasMetadata item, CancellationToken cancellationToken) { var path = GetSavePath(item); - + using (var memoryStream = new MemoryStream()) { Save(item, memoryStream, path); @@ -275,6 +275,10 @@ namespace MediaBrowser.XbmcMetadata.Savers { } + catch (XmlException ex) + { + Logger.ErrorException("Error reading existng nfo", ex); + } writer.WriteEndElement(); @@ -466,8 +470,8 @@ namespace MediaBrowser.XbmcMetadata.Savers { writer.WriteElementString("originaltitle", hasOriginalTitle.OriginalTitle ?? string.Empty); } - } - + } + var directors = item.People .Where(i => IsPersonType(i, PersonType.Director)) .Select(i => i.Name) @@ -978,7 +982,7 @@ namespace MediaBrowser.XbmcMetadata.Savers { writer.WriteElementString("type", person.Type); } - + if (person.SortOrder.HasValue) { writer.WriteElementString("sortorder", person.SortOrder.Value.ToString(UsCulture)); From 2626b6f3729097e083f381936738d17335f48f1c Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 31 Mar 2015 15:35:58 -0400 Subject: [PATCH 33/52] 3.0.5569.0 --- SharedVersion.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SharedVersion.cs b/SharedVersion.cs index d744ccd55f..e530e59cb7 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,4 +1,4 @@ using System.Reflection; -[assembly: AssemblyVersion("3.0.*")] -//[assembly: AssemblyVersion("3.0.5557.40000")] +//[assembly: AssemblyVersion("3.0.*")] +[assembly: AssemblyVersion("3.0.5569.0")] From ddbbe9ce4e054565272a69e1ea709f8df4d96479 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 1 Apr 2015 00:23:34 -0400 Subject: [PATCH 34/52] post release housecleaning --- MediaBrowser.Api/ApiEntryPoint.cs | 2 +- MediaBrowser.Api/ConfigurationService.cs | 2 +- .../Playback/TranscodingThrottler.cs | 2 +- MediaBrowser.Controller/Channels/Channel.cs | 15 ++-------- MediaBrowser.Controller/Entities/BaseItem.cs | 29 ------------------- MediaBrowser.Controller/Entities/Folder.cs | 17 ++--------- .../Entities/LinkedChild.cs | 3 -- .../Providers/BaseItemXmlParser.cs | 14 +-------- MediaBrowser.LocalMetadata/BaseXmlProvider.cs | 2 +- .../Savers/XmlSaverHelpers.cs | 5 ---- .../Configuration/ServerConfiguration.cs | 5 ++-- .../Configuration/UserConfiguration.cs | 7 ----- MediaBrowser.Model/Dlna/StreamInfo.cs | 5 ++++ .../Notifications/NotificationOptions.cs | 5 ++-- MediaBrowser.Model/Users/UserPolicy.cs | 2 -- .../HttpServer/Security/AuthService.cs | 2 +- .../Library/UserManager.cs | 26 ++--------------- .../Notifications/NotificationManager.cs | 2 +- .../FFMpeg/FFMpegDownloadInfo.cs | 18 ++++++------ .../Migrations/RenameXmlOptions.cs | 7 ++++- .../Savers/BaseNfoSaver.cs | 10 ------- 21 files changed, 39 insertions(+), 141 deletions(-) diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index 08ac5671d5..0db56e299f 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/MediaBrowser.Api/ApiEntryPoint.cs @@ -296,7 +296,7 @@ namespace MediaBrowser.Api // TODO: Lower this hls timeout var timerDuration = job.Type == TranscodingJobType.Progressive ? 1000 : - 7200000; + 1800000; if (job.KillTimer == null) { diff --git a/MediaBrowser.Api/ConfigurationService.cs b/MediaBrowser.Api/ConfigurationService.cs index d0abd18c28..9f6c07dd21 100644 --- a/MediaBrowser.Api/ConfigurationService.cs +++ b/MediaBrowser.Api/ConfigurationService.cs @@ -123,7 +123,7 @@ namespace MediaBrowser.Api public void Post(AutoSetMetadataOptions request) { - _configurationManager.DisableMetadataService("Media Browser Xml"); + _configurationManager.DisableMetadataService("Emby Xml"); _configurationManager.SaveConfiguration(); } diff --git a/MediaBrowser.Api/Playback/TranscodingThrottler.cs b/MediaBrowser.Api/Playback/TranscodingThrottler.cs index 58cfa086e3..ff79bb48f4 100644 --- a/MediaBrowser.Api/Playback/TranscodingThrottler.cs +++ b/MediaBrowser.Api/Playback/TranscodingThrottler.cs @@ -42,7 +42,7 @@ namespace MediaBrowser.Api.Playback var options = GetOptions(); - if (options.EnableThrottling && IsThrottleAllowed(_job, options.ThrottleThresholdSeconds)) + if (/*options.EnableThrottling &&*/ IsThrottleAllowed(_job, options.ThrottleThresholdSeconds)) { PauseTranscoding(); } diff --git a/MediaBrowser.Controller/Channels/Channel.cs b/MediaBrowser.Controller/Channels/Channel.cs index b6514ca0a6..f746d87fff 100644 --- a/MediaBrowser.Controller/Channels/Channel.cs +++ b/MediaBrowser.Controller/Channels/Channel.cs @@ -5,7 +5,6 @@ using System; using System.Linq; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Model.Users; namespace MediaBrowser.Controller.Channels { @@ -15,19 +14,9 @@ namespace MediaBrowser.Controller.Channels public override bool IsVisible(User user) { - if (user.Policy.BlockedChannels != null) + if (!user.Policy.EnableAllChannels && !user.Policy.EnabledChannels.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase)) { - if (user.Policy.BlockedChannels.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase)) - { - return false; - } - } - else - { - if (!user.Policy.EnableAllChannels && !user.Policy.EnabledChannels.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase)) - { - return false; - } + return false; } return base.IsVisible(user); diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index cdb52ec668..94fc761253 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -1219,18 +1219,6 @@ namespace MediaBrowser.Controller.Entities private BaseItem FindLinkedChild(LinkedChild info) { - if (!string.IsNullOrWhiteSpace(info.ItemName)) - { - if (string.Equals(info.ItemType, "musicgenre", StringComparison.OrdinalIgnoreCase)) - { - return LibraryManager.GetMusicGenre(info.ItemName); - } - if (string.Equals(info.ItemType, "musicartist", StringComparison.OrdinalIgnoreCase)) - { - return LibraryManager.GetArtist(info.ItemName); - } - } - if (!string.IsNullOrEmpty(info.Path)) { var itemByPath = LibraryManager.RootFolder.FindByPath(info.Path); @@ -1243,23 +1231,6 @@ namespace MediaBrowser.Controller.Entities return itemByPath; } - if (!string.IsNullOrWhiteSpace(info.ItemName) && !string.IsNullOrWhiteSpace(info.ItemType)) - { - return LibraryManager.RootFolder.GetRecursiveChildren(i => - { - if (string.Equals(i.Name, info.ItemName, StringComparison.OrdinalIgnoreCase)) - { - if (string.Equals(i.GetType().Name, info.ItemType, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - } - - return false; - - }).FirstOrDefault(); - } - return null; } diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index cffc0989a5..14095f7ffc 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -334,22 +334,9 @@ namespace MediaBrowser.Controller.Entities { if (this is ICollectionFolder && !(this is BasePluginFolder)) { - if (user.Policy.BlockedMediaFolders != null) + if (!user.Policy.EnableAllFolders && !user.Policy.EnabledFolders.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase)) { - if (user.Policy.BlockedMediaFolders.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase) || - - // Backwards compatibility - user.Policy.BlockedMediaFolders.Contains(Name, StringComparer.OrdinalIgnoreCase)) - { - return false; - } - } - else - { - if (!user.Policy.EnableAllFolders && !user.Policy.EnabledFolders.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase)) - { - return false; - } + return false; } } diff --git a/MediaBrowser.Controller/Entities/LinkedChild.cs b/MediaBrowser.Controller/Entities/LinkedChild.cs index 949c9741b1..ac13657b94 100644 --- a/MediaBrowser.Controller/Entities/LinkedChild.cs +++ b/MediaBrowser.Controller/Entities/LinkedChild.cs @@ -9,9 +9,6 @@ namespace MediaBrowser.Controller.Entities public string Path { get; set; } public LinkedChildType Type { get; set; } - public string ItemName { get; set; } - public string ItemType { get; set; } - [IgnoreDataMember] public string Id { get; set; } diff --git a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs index 6facc1074a..13f83c0fc9 100644 --- a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs +++ b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs @@ -1404,24 +1404,12 @@ namespace MediaBrowser.Controller.Providers { switch (reader.Name) { - case "Name": - { - linkedItem.ItemName = reader.ReadElementContentAsString(); - break; - } - case "Path": { linkedItem.Path = reader.ReadElementContentAsString(); break; } - case "Type": - { - linkedItem.ItemType = reader.ReadElementContentAsString(); - break; - } - default: reader.Skip(); break; @@ -1435,7 +1423,7 @@ namespace MediaBrowser.Controller.Providers return linkedItem; } - return string.IsNullOrWhiteSpace(linkedItem.ItemName) || string.IsNullOrWhiteSpace(linkedItem.ItemType) ? null : linkedItem; + return null; } diff --git a/MediaBrowser.LocalMetadata/BaseXmlProvider.cs b/MediaBrowser.LocalMetadata/BaseXmlProvider.cs index afe4b5799b..154d026008 100644 --- a/MediaBrowser.LocalMetadata/BaseXmlProvider.cs +++ b/MediaBrowser.LocalMetadata/BaseXmlProvider.cs @@ -92,7 +92,7 @@ namespace MediaBrowser.LocalMetadata { get { - return "Media Browser Xml"; + return "Emby Xml"; } } diff --git a/MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs b/MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs index c59d574bf4..1b98e75bef 100644 --- a/MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs +++ b/MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs @@ -756,11 +756,6 @@ namespace MediaBrowser.LocalMetadata.Savers { builder.Append("<" + singularNodeName + ">"); - if (!string.IsNullOrWhiteSpace(link.ItemType)) - { - builder.Append("" + SecurityElement.Escape(link.ItemType) + ""); - } - if (!string.IsNullOrWhiteSpace(link.Path)) { builder.Append("" + SecurityElement.Escape((link.Path)) + ""); diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index c06aedb500..bc167333ae 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -200,7 +200,7 @@ namespace MediaBrowser.Model.Configuration public PeopleMetadataOptions PeopleMetadataOptions { get; set; } public bool FindInternetTrailers { get; set; } - public string[] InsecureApps8 { get; set; } + public string[] InsecureApps9 { get; set; } public bool SaveMetadataHidden { get; set; } @@ -257,7 +257,7 @@ namespace MediaBrowser.Model.Configuration PeopleMetadataOptions = new PeopleMetadataOptions(); - InsecureApps8 = new[] + InsecureApps9 = new[] { "Chromecast", "iOS", @@ -266,7 +266,6 @@ namespace MediaBrowser.Model.Configuration "Media Portal", "iPad", "iPhone", - "Roku", "Windows Phone" }; diff --git a/MediaBrowser.Model/Configuration/UserConfiguration.cs b/MediaBrowser.Model/Configuration/UserConfiguration.cs index 98641636ae..a361da18b9 100644 --- a/MediaBrowser.Model/Configuration/UserConfiguration.cs +++ b/MediaBrowser.Model/Configuration/UserConfiguration.cs @@ -6,12 +6,6 @@ namespace MediaBrowser.Model.Configuration /// public class UserConfiguration { - /// - /// Gets or sets a value indicating whether this instance is administrator. - /// - /// true if this instance is administrator; otherwise, false. - public bool IsAdministrator { get; set; } - /// /// Gets or sets the audio language preference. /// @@ -53,7 +47,6 @@ namespace MediaBrowser.Model.Configuration public string[] LatestItemsExcludes { get; set; } - public bool HasMigratedToPolicy { get; set; } public bool HidePlayedInLatest { get; set; } /// diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index 3977a9ab07..c3e868cd49 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -217,6 +217,11 @@ namespace MediaBrowser.Model.Dlna return list; } + public List GetExternalSubtitles(bool includeSelectedTrackOnly, string baseUrl, string accessToken) + { + return GetExternalSubtitles(includeSelectedTrackOnly, false, baseUrl, accessToken); + } + public List GetExternalSubtitles(bool includeSelectedTrackOnly, bool enableAllProfiles, string baseUrl, string accessToken) { List list = GetSubtitleProfiles(includeSelectedTrackOnly, enableAllProfiles, baseUrl, accessToken); diff --git a/MediaBrowser.Model/Notifications/NotificationOptions.cs b/MediaBrowser.Model/Notifications/NotificationOptions.cs index e57955c9e2..683f1a76c8 100644 --- a/MediaBrowser.Model/Notifications/NotificationOptions.cs +++ b/MediaBrowser.Model/Notifications/NotificationOptions.cs @@ -1,5 +1,6 @@ using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Extensions; +using MediaBrowser.Model.Users; namespace MediaBrowser.Model.Notifications { @@ -106,7 +107,7 @@ namespace MediaBrowser.Model.Notifications !ListHelper.ContainsIgnoreCase(opt.DisabledMonitorUsers, userId); } - public bool IsEnabledToSendToUser(string type, string userId, UserConfiguration userConfig) + public bool IsEnabledToSendToUser(string type, string userId, UserPolicy userPolicy) { NotificationOption opt = GetOptions(type); @@ -117,7 +118,7 @@ namespace MediaBrowser.Model.Notifications return true; } - if (opt.SendToUserMode == SendToUserType.Admins && userConfig.IsAdministrator) + if (opt.SendToUserMode == SendToUserType.Admins && userPolicy.IsAdministrator) { return true; } diff --git a/MediaBrowser.Model/Users/UserPolicy.cs b/MediaBrowser.Model/Users/UserPolicy.cs index 7efc2cf6f6..738f5bffa9 100644 --- a/MediaBrowser.Model/Users/UserPolicy.cs +++ b/MediaBrowser.Model/Users/UserPolicy.cs @@ -32,8 +32,6 @@ namespace MediaBrowser.Model.Users public bool EnableUserPreferenceAccess { get; set; } public AccessSchedule[] AccessSchedules { get; set; } public UnratedItem[] BlockUnratedItems { get; set; } - public string[] BlockedMediaFolders { get; set; } - public string[] BlockedChannels { get; set; } public bool EnableRemoteControlOfOtherUsers { get; set; } public bool EnableSharedDeviceControl { get; set; } diff --git a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs index 3903c62b1e..b4da407026 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs @@ -140,7 +140,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security return true; } - return _config.Configuration.InsecureApps8.Contains(auth.Client ?? string.Empty, + return _config.Configuration.InsecureApps9.Contains(auth.Client ?? string.Empty, StringComparer.OrdinalIgnoreCase); } diff --git a/MediaBrowser.Server.Implementations/Library/UserManager.cs b/MediaBrowser.Server.Implementations/Library/UserManager.cs index b101f6ae1a..03471a8e9b 100644 --- a/MediaBrowser.Server.Implementations/Library/UserManager.cs +++ b/MediaBrowser.Server.Implementations/Library/UserManager.cs @@ -166,11 +166,6 @@ namespace MediaBrowser.Server.Implementations.Library var users = Users.ToList(); - foreach (var user in users) - { - await DoPolicyMigration(user).ConfigureAwait(false); - } - // If there are no local users with admin rights, make them all admins if (!users.Any(i => i.Policy.IsAdministrator)) { @@ -286,10 +281,10 @@ namespace MediaBrowser.Server.Implementations.Library if (newValue >= maxCount) { - //_logger.Debug("Disabling user {0} due to {1} unsuccessful login attempts.", user.Name, newValue.ToString(CultureInfo.InvariantCulture)); - //user.Policy.IsDisabled = true; + _logger.Debug("Disabling user {0} due to {1} unsuccessful login attempts.", user.Name, newValue.ToString(CultureInfo.InvariantCulture)); + user.Policy.IsDisabled = true; - //fireLockout = true; + fireLockout = true; } await UpdateUserPolicy(user, user.Policy, false).ConfigureAwait(false); @@ -366,19 +361,6 @@ namespace MediaBrowser.Server.Implementations.Library return users; } - private async Task DoPolicyMigration(User user) - { - if (!user.Configuration.HasMigratedToPolicy) - { - user.Policy.IsAdministrator = user.Configuration.IsAdministrator; - - await UpdateUserPolicy(user, user.Policy, false); - - user.Configuration.HasMigratedToPolicy = true; - await UpdateConfiguration(user, user.Configuration, true).ConfigureAwait(false); - } - } - public UserDto GetUserDto(User user, string remoteEndPoint = null) { if (user == null) @@ -953,8 +935,6 @@ namespace MediaBrowser.Server.Implementations.Library user.Policy = userPolicy; } - user.Configuration.IsAdministrator = user.Policy.IsAdministrator; - await UpdateConfiguration(user, user.Configuration, true).ConfigureAwait(false); } diff --git a/MediaBrowser.Server.Implementations/Notifications/NotificationManager.cs b/MediaBrowser.Server.Implementations/Notifications/NotificationManager.cs index 5e02b2bacb..1ff928cd5b 100644 --- a/MediaBrowser.Server.Implementations/Notifications/NotificationManager.cs +++ b/MediaBrowser.Server.Implementations/Notifications/NotificationManager.cs @@ -95,7 +95,7 @@ namespace MediaBrowser.Server.Implementations.Notifications var config = GetConfiguration(); return _userManager.Users - .Where(i => config.IsEnabledToSendToUser(request.NotificationType, i.Id.ToString("N"), i.Configuration)) + .Where(i => config.IsEnabledToSendToUser(request.NotificationType, i.Id.ToString("N"), i.Policy)) .Select(i => i.Id.ToString("N")); } diff --git a/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegDownloadInfo.cs b/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegDownloadInfo.cs index cabb8dc839..4d2ed067be 100644 --- a/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegDownloadInfo.cs +++ b/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegDownloadInfo.cs @@ -22,7 +22,7 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg var info = new FFMpegDownloadInfo(); // Windows builds: http://ffmpeg.zeranoe.com/builds/ - // Linux builds: http://ffmpeg.gusari.org/static/ + // Linux builds: http://johnvansickle.com/ffmpeg/ // OS X builds: http://ffmpegmac.net/ // OS X x64: http://www.evermeet.cx/ffmpeg/ @@ -33,7 +33,7 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg case OperatingSystem.Linux: info.ArchiveType = "7z"; - info.Version = "20150124"; + info.Version = "20150331"; break; case OperatingSystem.Osx: @@ -54,7 +54,7 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg info.FFMpegFilename = "ffmpeg.exe"; info.FFProbeFilename = "ffprobe.exe"; - info.Version = "20150110"; + info.Version = "20150331"; info.ArchiveType = "7z"; switch (environment.SystemArchitecture) @@ -83,14 +83,14 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg case Architecture.X86_X64: return new[] { - "http://ffmpeg.zeranoe.com/builds/win64/static/ffmpeg-20150110-git-4df01d5-win64-static.7z", - "https://github.com/MediaBrowser/MediaBrowser.Resources/raw/master/ffmpeg/windows/ffmpeg-20150110-git-4df01d5-win64-static.7z" + "http://ffmpeg.zeranoe.com/builds/win64/static/ffmpeg-20150331-git-5cba529-win64-static.7z", + "https://github.com/MediaBrowser/MediaBrowser.Resources/raw/master/ffmpeg/windows/ffmpeg-20150331-git-5cba529-win64-static.7z" }; case Architecture.X86: return new[] { - "http://ffmpeg.zeranoe.com/builds/win32/static/ffmpeg-20150110-git-4df01d5-win32-static.7z", - "https://github.com/MediaBrowser/MediaBrowser.Resources/raw/master/ffmpeg/windows/ffmpeg-20150110-git-4df01d5-win32-static.7z" + "http://ffmpeg.zeranoe.com/builds/win32/static/ffmpeg-20150331-git-5cba529-win32-static.7z", + "https://github.com/MediaBrowser/MediaBrowser.Resources/raw/master/ffmpeg/windows/ffmpeg-20150331-git-5cba529-win32-static.7z" }; } break; @@ -119,12 +119,12 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg case Architecture.X86_X64: return new[] { - "https://github.com/MediaBrowser/MediaBrowser.Resources/raw/master/ffmpeg/linux/ffmpeg-2.5.3-64bit-static.7z" + "https://github.com/MediaBrowser/MediaBrowser.Resources/raw/master/ffmpeg/linux/ffmpeg-2.6.1-64bit-static.7z" }; case Architecture.X86: return new[] { - "https://github.com/MediaBrowser/MediaBrowser.Resources/raw/master/ffmpeg/linux/ffmpeg-2.5.3-32bit-static.7z" + "https://github.com/MediaBrowser/MediaBrowser.Resources/raw/master/ffmpeg/linux/ffmpeg-2.6.1-32bit-static.7z" }; } break; diff --git a/MediaBrowser.Server.Startup.Common/Migrations/RenameXmlOptions.cs b/MediaBrowser.Server.Startup.Common/Migrations/RenameXmlOptions.cs index be8ae2f814..49114b96f3 100644 --- a/MediaBrowser.Server.Startup.Common/Migrations/RenameXmlOptions.cs +++ b/MediaBrowser.Server.Startup.Common/Migrations/RenameXmlOptions.cs @@ -44,7 +44,12 @@ namespace MediaBrowser.Server.Startup.Common.Migrations { if (string.Equals(options[i], "Media Browser Legacy Xml", StringComparison.OrdinalIgnoreCase)) { - options[i] = "Media Browser Xml"; + options[i] = "Emby Xml"; + changed = true; + } + else if (string.Equals(options[i], "Media Browser Xml", StringComparison.OrdinalIgnoreCase)) + { + options[i] = "Emby Xml"; changed = true; } } diff --git a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs index 5c4319d228..9cde958c46 100644 --- a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs @@ -856,16 +856,6 @@ namespace MediaBrowser.XbmcMetadata.Savers { writer.WriteStartElement("collectionitem"); - if (!string.IsNullOrWhiteSpace(link.ItemName)) - { - writer.WriteElementString("name", link.ItemName); - } - - if (!string.IsNullOrWhiteSpace(link.ItemType)) - { - writer.WriteElementString("type", link.ItemType); - } - if (!string.IsNullOrWhiteSpace(link.Path)) { writer.WriteElementString("path", link.Path); From 42e236915041a63d0b9944b8400cc121a4f3cba4 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 1 Apr 2015 17:55:50 -0400 Subject: [PATCH 35/52] add missing PlaySessionId --- MediaBrowser.Api/Playback/MediaInfoService.cs | 18 ++++++++++-------- MediaBrowser.Model/Dlna/StreamInfo.cs | 10 +++++++--- .../MediaInfo/LiveStreamRequest.cs | 3 ++- .../Session/PlaybackProgressInfo.cs | 5 +++++ MediaBrowser.Model/Session/PlaybackStopInfo.cs | 5 +++++ 5 files changed, 29 insertions(+), 12 deletions(-) diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs index ca735f0684..46a1ff7890 100644 --- a/MediaBrowser.Api/Playback/MediaInfoService.cs +++ b/MediaBrowser.Api/Playback/MediaInfoService.cs @@ -101,7 +101,7 @@ namespace MediaBrowser.Api.Playback SetDeviceSpecificData(item, result.MediaSource, profile, authInfo, request.MaxStreamingBitrate, request.StartTimeTicks ?? 0, result.MediaSource.Id, request.AudioStreamIndex, - request.SubtitleStreamIndex); + request.SubtitleStreamIndex, request.PlaySessionId); } else { @@ -206,7 +206,7 @@ namespace MediaBrowser.Api.Playback foreach (var mediaSource in result.MediaSources) { - SetDeviceSpecificData(item, mediaSource, profile, auth, maxBitrate, startTimeTicks, mediaSourceId, audioStreamIndex, subtitleStreamIndex); + SetDeviceSpecificData(item, mediaSource, profile, auth, maxBitrate, startTimeTicks, mediaSourceId, audioStreamIndex, subtitleStreamIndex, result.PlaySessionId); } SortMediaSources(result); @@ -220,7 +220,8 @@ namespace MediaBrowser.Api.Playback long startTimeTicks, string mediaSourceId, int? audioStreamIndex, - int? subtitleStreamIndex) + int? subtitleStreamIndex, + string playSessionId) { var streamBuilder = new StreamBuilder(); @@ -294,6 +295,12 @@ namespace MediaBrowser.Api.Playback streamBuilder.BuildAudioItem(options) : streamBuilder.BuildVideoItem(options); + if (streamInfo != null) + { + streamInfo.PlaySessionId = playSessionId; + SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, baseUrl, auth.Token); + } + if (streamInfo != null && streamInfo.PlayMethod == PlayMethod.Transcode) { streamInfo.StartPositionTicks = startTimeTicks; @@ -301,11 +308,6 @@ namespace MediaBrowser.Api.Playback mediaSource.TranscodingContainer = streamInfo.Container; mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol; } - - if (streamInfo != null) - { - SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, baseUrl, auth.Token); - } } } diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index 3977a9ab07..a2f5fcb104 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -69,7 +69,7 @@ namespace MediaBrowser.Model.Dlna public SubtitleDeliveryMethod SubtitleDeliveryMethod { get; set; } public string SubtitleFormat { get; set; } - public PlaybackInfoResponse PlaybackInfo { get; set; } + public string PlaySessionId { get; set; } public string MediaSourceId { @@ -207,8 +207,7 @@ namespace MediaBrowser.Model.Dlna list.Add(new NameValuePair("Profile", item.VideoProfile ?? string.Empty)); list.Add(new NameValuePair("Cabac", item.Cabac.HasValue ? item.Cabac.Value.ToString() : string.Empty)); - string playSessionId = item.PlaybackInfo == null ? null : item.PlaybackInfo.PlaySessionId; - list.Add(new NameValuePair("PlaySessionId", playSessionId ?? string.Empty)); + list.Add(new NameValuePair("PlaySessionId", item.PlaySessionId ?? string.Empty)); list.Add(new NameValuePair("api_key", accessToken ?? string.Empty)); string liveStreamId = item.MediaSource == null ? null : item.MediaSource.LiveStreamId; @@ -217,6 +216,11 @@ namespace MediaBrowser.Model.Dlna return list; } + public List GetExternalSubtitles(bool includeSelectedTrackOnly, string baseUrl, string accessToken) + { + return GetExternalSubtitles(includeSelectedTrackOnly, false, baseUrl, accessToken); + } + public List GetExternalSubtitles(bool includeSelectedTrackOnly, bool enableAllProfiles, string baseUrl, string accessToken) { List list = GetSubtitleProfiles(includeSelectedTrackOnly, enableAllProfiles, baseUrl, accessToken); diff --git a/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs b/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs index 563006a4df..3affbbcc34 100644 --- a/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs +++ b/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs @@ -6,6 +6,7 @@ namespace MediaBrowser.Model.MediaInfo { public string OpenToken { get; set; } public string UserId { get; set; } + public string PlaySessionId { get; set; } public int? MaxStreamingBitrate { get; set; } public long? StartTimeTicks { get; set; } public int? AudioStreamIndex { get; set; } @@ -15,7 +16,7 @@ namespace MediaBrowser.Model.MediaInfo public LiveStreamRequest() { - + } public LiveStreamRequest(AudioOptions options) diff --git a/MediaBrowser.Model/Session/PlaybackProgressInfo.cs b/MediaBrowser.Model/Session/PlaybackProgressInfo.cs index a029df4a93..a7ca09c15a 100644 --- a/MediaBrowser.Model/Session/PlaybackProgressInfo.cs +++ b/MediaBrowser.Model/Session/PlaybackProgressInfo.cs @@ -83,5 +83,10 @@ namespace MediaBrowser.Model.Session /// /// The live stream identifier. public string LiveStreamId { get; set; } + /// + /// Gets or sets the play session identifier. + /// + /// The play session identifier. + public string PlaySessionId { get; set; } } } \ No newline at end of file diff --git a/MediaBrowser.Model/Session/PlaybackStopInfo.cs b/MediaBrowser.Model/Session/PlaybackStopInfo.cs index a3bdc9a960..80d719f037 100644 --- a/MediaBrowser.Model/Session/PlaybackStopInfo.cs +++ b/MediaBrowser.Model/Session/PlaybackStopInfo.cs @@ -41,5 +41,10 @@ namespace MediaBrowser.Model.Session /// /// The live stream identifier. public string LiveStreamId { get; set; } + /// + /// Gets or sets the play session identifier. + /// + /// The play session identifier. + public string PlaySessionId { get; set; } } } \ No newline at end of file From b9f3944b7f2cbfba1dadffd0ac0594ad3cd81f38 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 1 Apr 2015 17:56:32 -0400 Subject: [PATCH 36/52] rework media source methods --- .../Library/IMediaSourceManager.cs | 10 +------ MediaBrowser.Dlna/Didl/DidlBuilder.cs | 4 +-- MediaBrowser.Dlna/PlayTo/PlayToController.cs | 2 +- .../Library/MediaSourceManager.cs | 29 ++++--------------- SharedVersion.cs | 4 +-- 5 files changed, 12 insertions(+), 37 deletions(-) diff --git a/MediaBrowser.Controller/Library/IMediaSourceManager.cs b/MediaBrowser.Controller/Library/IMediaSourceManager.cs index 9cbbabc8db..5bcc5f3136 100644 --- a/MediaBrowser.Controller/Library/IMediaSourceManager.cs +++ b/MediaBrowser.Controller/Library/IMediaSourceManager.cs @@ -63,15 +63,7 @@ namespace MediaBrowser.Controller.Library /// if set to true [enable path substitution]. /// The user. /// IEnumerable<MediaSourceInfo>. - IEnumerable GetStaticMediaSources(IHasMediaSources item, bool enablePathSubstitution, User user); - - /// - /// Gets the static media sources. - /// - /// The item. - /// if set to true [enable path substitution]. - /// IEnumerable<MediaSourceInfo>. - IEnumerable GetStaticMediaSources(IHasMediaSources item, bool enablePathSubstitution); + IEnumerable GetStaticMediaSources(IHasMediaSources item, bool enablePathSubstitution, User user = null); /// /// Gets the static media source. diff --git a/MediaBrowser.Dlna/Didl/DidlBuilder.cs b/MediaBrowser.Dlna/Didl/DidlBuilder.cs index 3b1cdb5428..19dab2246d 100644 --- a/MediaBrowser.Dlna/Didl/DidlBuilder.cs +++ b/MediaBrowser.Dlna/Didl/DidlBuilder.cs @@ -124,7 +124,7 @@ namespace MediaBrowser.Dlna.Didl { if (streamInfo == null) { - var sources = _user == null ? _mediaSourceManager.GetStaticMediaSources(video, true).ToList() : _mediaSourceManager.GetStaticMediaSources(video, true, _user).ToList(); + var sources = _mediaSourceManager.GetStaticMediaSources(video, true, _user).ToList(); streamInfo = new StreamBuilder().BuildVideoItem(new VideoOptions { @@ -351,7 +351,7 @@ namespace MediaBrowser.Dlna.Didl if (streamInfo == null) { - var sources = _user == null ? _mediaSourceManager.GetStaticMediaSources(audio, true).ToList() : _mediaSourceManager.GetStaticMediaSources(audio, true, _user).ToList(); + var sources = _mediaSourceManager.GetStaticMediaSources(audio, true, _user).ToList(); streamInfo = new StreamBuilder().BuildAudioItem(new AudioOptions { diff --git a/MediaBrowser.Dlna/PlayTo/PlayToController.cs b/MediaBrowser.Dlna/PlayTo/PlayToController.cs index 38c0f71cc7..5b129243c4 100644 --- a/MediaBrowser.Dlna/PlayTo/PlayToController.cs +++ b/MediaBrowser.Dlna/PlayTo/PlayToController.cs @@ -470,7 +470,7 @@ namespace MediaBrowser.Dlna.PlayTo var hasMediaSources = item as IHasMediaSources; var mediaSources = hasMediaSources != null - ? (user == null ? _mediaSourceManager.GetStaticMediaSources(hasMediaSources, true) : _mediaSourceManager.GetStaticMediaSources(hasMediaSources, true, user)).ToList() + ? (_mediaSourceManager.GetStaticMediaSources(hasMediaSources, true, user)).ToList() : new List(); var playlistItem = GetPlaylistItem(item, mediaSources, profile, _session.DeviceId, mediaSourceId, audioStreamIndex, subtitleStreamIndex); diff --git a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs index 4fab95263f..27a7d4ea97 100644 --- a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs +++ b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs @@ -235,7 +235,7 @@ namespace MediaBrowser.Server.Implementations.Library return GetStaticMediaSources(item, enablePathSubstitution).FirstOrDefault(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase)); } - public IEnumerable GetStaticMediaSources(IHasMediaSources item, bool enablePathSubstitution) + public IEnumerable GetStaticMediaSources(IHasMediaSources item, bool enablePathSubstitution, User user = null) { if (item == null) { @@ -247,31 +247,14 @@ namespace MediaBrowser.Server.Implementations.Library return item.GetMediaSources(enablePathSubstitution); } - return item.GetMediaSources(enablePathSubstitution); - } - - public IEnumerable GetStaticMediaSources(IHasMediaSources item, bool enablePathSubstitution, User user) - { - if (item == null) - { - throw new ArgumentNullException("item"); - } - - if (!(item is Video)) - { - return item.GetMediaSources(enablePathSubstitution); - } - - if (user == null) - { - throw new ArgumentNullException("user"); - } - var sources = item.GetMediaSources(enablePathSubstitution).ToList(); - foreach (var source in sources) + if (user != null) { - SetUserProperties(source, user); + foreach (var source in sources) + { + SetUserProperties(source, user); + } } return sources; diff --git a/SharedVersion.cs b/SharedVersion.cs index e530e59cb7..11c0c6b5e6 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,4 +1,4 @@ using System.Reflection; -//[assembly: AssemblyVersion("3.0.*")] -[assembly: AssemblyVersion("3.0.5569.0")] +[assembly: AssemblyVersion("3.0.*")] +//[assembly: AssemblyVersion("3.0.5569.0")] From 967751d2a59db10dd91aa3cf31b0922a19269b5c Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 1 Apr 2015 21:34:14 -0400 Subject: [PATCH 37/52] rework hls timer --- MediaBrowser.Api/ApiEntryPoint.cs | 58 +++++++++++++++---- .../UserLibrary/PlaystateService.cs | 10 ++++ 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index 0db56e299f..ed5fa5bfd7 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/MediaBrowser.Api/ApiEntryPoint.cs @@ -286,27 +286,65 @@ namespace MediaBrowser.Api job.DisposeKillTimer(); } - + public void OnTranscodeEndRequest(TranscodingJob job) { job.ActiveRequestCount--; if (job.ActiveRequestCount == 0) { - // TODO: Lower this hls timeout - var timerDuration = job.Type == TranscodingJobType.Progressive ? - 1000 : - 1800000; + PingTimer(job, true); + } + } + internal void PingTranscodingJob(string deviceId, string playSessionId) + { + if (string.IsNullOrEmpty(deviceId)) + { + throw new ArgumentNullException("deviceId"); + } - if (job.KillTimer == null) + var jobs = new List(); + + lock (_activeTranscodingJobs) + { + // This is really only needed for HLS. + // Progressive streams can stop on their own reliably + jobs = jobs.Where(j => { - job.KillTimer = new Timer(OnTranscodeKillTimerStopped, job, timerDuration, Timeout.Infinite); - } - else + if (string.Equals(deviceId, j.DeviceId, StringComparison.OrdinalIgnoreCase)) + { + return string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase); + } + + return false; + + }).ToList(); + } + + foreach (var job in jobs) + { + PingTimer(job, false); + } + } + + private void PingTimer(TranscodingJob job, bool startTimerIfNeeded) + { + // TODO: Lower this hls timeout + var timerDuration = job.Type == TranscodingJobType.Progressive ? + 1000 : + 1800000; + + if (job.KillTimer == null) + { + if (startTimerIfNeeded) { - job.KillTimer.Change(timerDuration, Timeout.Infinite); + job.KillTimer = new Timer(OnTranscodeKillTimerStopped, job, timerDuration, Timeout.Infinite); } } + else + { + job.KillTimer.Change(timerDuration, Timeout.Infinite); + } } /// diff --git a/MediaBrowser.Api/UserLibrary/PlaystateService.cs b/MediaBrowser.Api/UserLibrary/PlaystateService.cs index 55e1681e0d..6c767596ea 100644 --- a/MediaBrowser.Api/UserLibrary/PlaystateService.cs +++ b/MediaBrowser.Api/UserLibrary/PlaystateService.cs @@ -294,6 +294,11 @@ namespace MediaBrowser.Api.UserLibrary public void Post(ReportPlaybackProgress request) { + if (!string.IsNullOrWhiteSpace(request.PlaySessionId)) + { + ApiEntryPoint.Instance.PingTranscodingJob(AuthorizationContext.GetAuthorizationInfo(Request).DeviceId, request.PlaySessionId); + } + request.SessionId = GetSession().Result.Id; var task = _sessionManager.OnPlaybackProgress(request); @@ -317,6 +322,11 @@ namespace MediaBrowser.Api.UserLibrary public void Post(ReportPlaybackStopped request) { + if (!string.IsNullOrWhiteSpace(request.PlaySessionId)) + { + ApiEntryPoint.Instance.KillTranscodingJobs(AuthorizationContext.GetAuthorizationInfo(Request).DeviceId, request.PlaySessionId, s => true); + } + request.SessionId = GetSession().Result.Id; var task = _sessionManager.OnPlaybackStopped(request); From f8927ceb067365d89406263ec1ca9084eb10b8e0 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 1 Apr 2015 22:47:59 -0400 Subject: [PATCH 38/52] resolve reverse proxy issue --- MediaBrowser.Api/BaseApiService.cs | 11 ----------- MediaBrowser.Api/Playback/MediaInfoService.cs | 17 ++++++++--------- MediaBrowser.Model/Dlna/StreamInfo.cs | 3 +++ MediaBrowser.Model/Dlna/SubtitleStreamInfo.cs | 1 + MediaBrowser.Model/Entities/MediaStream.cs | 7 ++++++- 5 files changed, 18 insertions(+), 21 deletions(-) diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs index 3364c3c6be..4465be97a2 100644 --- a/MediaBrowser.Api/BaseApiService.cs +++ b/MediaBrowser.Api/BaseApiService.cs @@ -73,17 +73,6 @@ namespace MediaBrowser.Api return ResultFactory.GetOptimizedResultUsingCache(Request, cacheKey, lastDateModified, cacheDuration, factoryFn); } - /// - /// Infers the server address from the url - /// - /// - protected string GetServerAddress() - { - var index = Request.AbsoluteUri.IndexOf(Request.PathInfo, StringComparison.OrdinalIgnoreCase); - - return Request.AbsoluteUri.Substring(0, index); - } - protected void AssertCanUpdateUser(IUserManager userManager, string userId) { var auth = AuthorizationContext.GetAuthorizationInfo(Request); diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs index 46a1ff7890..24c9d637ad 100644 --- a/MediaBrowser.Api/Playback/MediaInfoService.cs +++ b/MediaBrowser.Api/Playback/MediaInfoService.cs @@ -225,8 +225,6 @@ namespace MediaBrowser.Api.Playback { var streamBuilder = new StreamBuilder(); - var baseUrl = GetServerAddress(); - var options = new VideoOptions { MediaSources = new List { mediaSource }, @@ -266,7 +264,7 @@ namespace MediaBrowser.Api.Playback if (streamInfo != null) { - SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, baseUrl, auth.Token); + SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token); } } @@ -284,7 +282,7 @@ namespace MediaBrowser.Api.Playback if (streamInfo != null) { - SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, baseUrl, auth.Token); + SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token); } } @@ -298,22 +296,22 @@ namespace MediaBrowser.Api.Playback if (streamInfo != null) { streamInfo.PlaySessionId = playSessionId; - SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, baseUrl, auth.Token); + SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token); } if (streamInfo != null && streamInfo.PlayMethod == PlayMethod.Transcode) { streamInfo.StartPositionTicks = startTimeTicks; - mediaSource.TranscodingUrl = streamInfo.ToUrl(baseUrl, auth.Token); + mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).TrimStart('-').TrimStart('-'); mediaSource.TranscodingContainer = streamInfo.Container; mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol; } } } - private void SetDeviceSpecificSubtitleInfo(StreamInfo info, MediaSourceInfo mediaSource, string baseUrl, string accessToken) + private void SetDeviceSpecificSubtitleInfo(StreamInfo info, MediaSourceInfo mediaSource, string accessToken) { - var profiles = info.GetSubtitleProfiles(false, baseUrl, accessToken); + var profiles = info.GetSubtitleProfiles(false, "-", accessToken); mediaSource.DefaultSubtitleStreamIndex = info.SubtitleStreamIndex; foreach (var profile in profiles) @@ -326,7 +324,8 @@ namespace MediaBrowser.Api.Playback if (profile.DeliveryMethod == SubtitleDeliveryMethod.External) { - stream.DeliveryUrl = profile.Url; + stream.DeliveryUrl = profile.Url.TrimStart('-').TrimStart('-'); + stream.IsExternalUrl = profile.IsExternalUrl; } } } diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index a2f5fcb104..a57bc0c0ef 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -321,10 +321,13 @@ namespace MediaBrowser.Model.Dlna StringHelper.ToStringCultureInvariant(stream.Index), StringHelper.ToStringCultureInvariant(startPositionTicks), subtitleProfile.Format); + + info.IsExternalUrl = false; } else { info.Url = stream.Path; + info.IsExternalUrl = true; } } diff --git a/MediaBrowser.Model/Dlna/SubtitleStreamInfo.cs b/MediaBrowser.Model/Dlna/SubtitleStreamInfo.cs index 602858ccc9..61b2895fc1 100644 --- a/MediaBrowser.Model/Dlna/SubtitleStreamInfo.cs +++ b/MediaBrowser.Model/Dlna/SubtitleStreamInfo.cs @@ -9,5 +9,6 @@ namespace MediaBrowser.Model.Dlna public string Format { get; set; } public int Index { get; set; } public SubtitleDeliveryMethod DeliveryMethod { get; set; } + public bool IsExternalUrl { get; set; } } } \ No newline at end of file diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index dfeed7450b..0f3435174c 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -135,7 +135,7 @@ namespace MediaBrowser.Model.Entities /// /// The score. public int? Score { get; set; } - + /// /// Gets or sets a value indicating whether this instance is external. /// @@ -152,6 +152,11 @@ namespace MediaBrowser.Model.Entities /// /// The delivery URL. public string DeliveryUrl { get; set; } + /// + /// Gets or sets a value indicating whether this instance is external URL. + /// + /// null if [is external URL] contains no value, true if [is external URL]; otherwise, false. + public bool? IsExternalUrl { get; set; } public bool IsTextSubtitleStream { From 72023207a43d66035d8868f986ceabdc8f1912f4 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 2 Apr 2015 12:24:59 -0400 Subject: [PATCH 39/52] fix hasMediaSources exception --- .../Session/SessionManager.cs | 35 ++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index cc5aef54aa..7f5033b98f 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -305,7 +305,7 @@ namespace MediaBrowser.Server.Implementations.Session } } - private async Task GetMediaSource(BaseItem item, string mediaSourceId) + private async Task GetMediaSource(IHasMediaSources item, string mediaSourceId) { var sources = await _mediaSourceManager.GetPlayackMediaSources(item.Id.ToString("N"), false, CancellationToken.None) .ConfigureAwait(false); @@ -334,11 +334,16 @@ namespace MediaBrowser.Server.Implementations.Session { var runtimeTicks = libraryItem.RunTimeTicks; - var mediaSource = await GetMediaSource(libraryItem, info.MediaSourceId).ConfigureAwait(false); - - if (mediaSource != null) + MediaSourceInfo mediaSource = null; + var hasMediaSources = libraryItem as IHasMediaSources; + if (hasMediaSources != null) { - runtimeTicks = mediaSource.RunTimeTicks; + mediaSource = await GetMediaSource(hasMediaSources, info.MediaSourceId).ConfigureAwait(false); + + if (mediaSource != null) + { + runtimeTicks = mediaSource.RunTimeTicks; + } } info.Item = GetItemInfo(libraryItem, libraryItem, mediaSource); @@ -414,12 +419,12 @@ namespace MediaBrowser.Server.Implementations.Session if (!_activeConnections.TryGetValue(key, out sessionInfo)) { sessionInfo = new SessionInfo - { - Client = appName, - DeviceId = deviceId, - ApplicationVersion = appVersion, - Id = key.GetMD5().ToString("N") - }; + { + Client = appName, + DeviceId = deviceId, + ApplicationVersion = appVersion, + Id = key.GetMD5().ToString("N") + }; sessionInfo.DeviceName = deviceName; sessionInfo.UserId = userId; @@ -756,7 +761,13 @@ namespace MediaBrowser.Server.Implementations.Session if (current == null || !string.Equals(current.Id, info.ItemId, StringComparison.OrdinalIgnoreCase)) { - var mediaSource = await GetMediaSource(libraryItem, info.MediaSourceId).ConfigureAwait(false); + MediaSourceInfo mediaSource = null; + + var hasMediaSources = libraryItem as IHasMediaSources; + if (hasMediaSources != null) + { + mediaSource = await GetMediaSource(hasMediaSources, info.MediaSourceId).ConfigureAwait(false); + } info.Item = GetItemInfo(libraryItem, libraryItem, mediaSource); } From 74adff0d8d63c4c764be0ccd7b1935480a2a2e0a Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 2 Apr 2015 12:26:42 -0400 Subject: [PATCH 40/52] combine movie fixes --- .../Playback/Dash/MpegDashService.cs | 14 +------------- MediaBrowser.Api/Playback/Hls/BaseHlsService.cs | 16 +++++++++++++++- .../Playback/Hls/DynamicHlsService.cs | 3 +-- .../Localization/Server/server.json | 1 + MediaBrowser.WebDashboard/Api/PackageCreator.cs | 1 - .../MediaBrowser.WebDashboard.csproj | 6 ------ 6 files changed, 18 insertions(+), 23 deletions(-) diff --git a/MediaBrowser.Api/Playback/Dash/MpegDashService.cs b/MediaBrowser.Api/Playback/Dash/MpegDashService.cs index ba3f172579..0692c4863e 100644 --- a/MediaBrowser.Api/Playback/Dash/MpegDashService.cs +++ b/MediaBrowser.Api/Playback/Dash/MpegDashService.cs @@ -5,7 +5,6 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Net; using MediaBrowser.Model.IO; @@ -518,25 +517,14 @@ namespace MediaBrowser.Api.Playback.Dash private async Task WaitForSegment(string playlist, string segment, CancellationToken cancellationToken) { - var tmpPath = playlist + ".tmp"; - var segmentFilename = Path.GetFileName(segment); Logger.Debug("Waiting for {0} in {1}", segmentFilename, playlist); while (true) { - FileStream fileStream; - try - { - fileStream = FileSystem.GetFileStream(tmpPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true); - } - catch (IOException) - { - fileStream = FileSystem.GetFileStream(playlist, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true); - } // Need to use FileShare.ReadWrite because we're reading the file at the same time it's being written - using (fileStream) + using (var fileStream = GetPlaylistFileStream(playlist)) { using (var reader = new StreamReader(fileStream)) { diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index 207bc2f679..657a97ac15 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -186,7 +186,7 @@ namespace MediaBrowser.Api.Playback.Hls while (true) { // Need to use FileShare.ReadWrite because we're reading the file at the same time it's being written - using (var fileStream = FileSystem.GetFileStream(playlist, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true)) + using (var fileStream = GetPlaylistFileStream(playlist)) { using (var reader = new StreamReader(fileStream)) { @@ -212,6 +212,20 @@ namespace MediaBrowser.Api.Playback.Hls } } + protected Stream GetPlaylistFileStream(string path) + { + var tmpPath = path + ".tmp"; + + try + { + return FileSystem.GetFileStream(tmpPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true); + } + catch (IOException) + { + return FileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true); + } + } + protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding) { var hlsVideoRequest = state.VideoRequest as GetHlsVideoStream; diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index 4f043d3217..dc5f9d4e0a 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -4,7 +4,6 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Dlna; @@ -300,7 +299,7 @@ namespace MediaBrowser.Api.Playback.Hls var segmentFilename = Path.GetFileName(segmentPath); - using (var fileStream = FileSystem.GetFileStream(playlistPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true)) + using (var fileStream = GetPlaylistFileStream(playlistPath)) { using (var reader = new StreamReader(fileStream)) { diff --git a/MediaBrowser.Server.Implementations/Localization/Server/server.json b/MediaBrowser.Server.Implementations/Localization/Server/server.json index 0cff99c5d1..d27403dda5 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/server.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/server.json @@ -168,6 +168,7 @@ "MessageNothingHere": "Nothing here.", "MessagePleaseEnsureInternetMetadata": "Please ensure downloading of internet metadata is enabled.", "TabSuggested": "Suggested", + "TabSuggestions": "Suggestions", "TabLatest": "Latest", "TabUpcoming": "Upcoming", "TabShows": "Shows", diff --git a/MediaBrowser.WebDashboard/Api/PackageCreator.cs b/MediaBrowser.WebDashboard/Api/PackageCreator.cs index 8982b5739c..5138b157f1 100644 --- a/MediaBrowser.WebDashboard/Api/PackageCreator.cs +++ b/MediaBrowser.WebDashboard/Api/PackageCreator.cs @@ -451,7 +451,6 @@ namespace MediaBrowser.WebDashboard.Api "moviegenres.js", "moviecollections.js", "movies.js", - "movieslatest.js", "moviepeople.js", "moviesrecommended.js", "moviestudios.js", diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index 2725d63ad8..88a2a8ae1b 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -556,9 +556,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest @@ -889,9 +886,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest From 285805d84a3d636b359e1a16f0d6b94fef6fc258 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 2 Apr 2015 12:58:52 -0400 Subject: [PATCH 41/52] create square collages --- .../Photos/BaseDynamicImageProvider.cs | 34 +++++----- .../UserViews/DynamicImageProvider.cs | 3 +- .../UserViews/StripCollageBuilder.cs | 64 +++++++++++++++++++ 3 files changed, 80 insertions(+), 21 deletions(-) diff --git a/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs b/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs index 1063fde530..0b5ffc903b 100644 --- a/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs +++ b/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs @@ -13,6 +13,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Server.Implementations.UserViews; namespace MediaBrowser.Server.Implementations.Photos { @@ -134,30 +135,25 @@ namespace MediaBrowser.Server.Implementations.Photos return parts.GetMD5().ToString("N"); } - protected Task GetThumbCollage(List items) + protected Task GetThumbCollage(IHasImages primaryItem, List items) { - var files = items - .Select(i => i.GetImagePath(ImageType.Primary) ?? i.GetImagePath(ImageType.Thumb)) - .Where(i => !string.IsNullOrWhiteSpace(i)) - .ToList(); + var stream = new StripCollageBuilder(ApplicationPaths).BuildThumbCollage(GetStripCollageImagePaths(items), primaryItem.Name, 960, 540); - return DynamicImageHelpers.GetThumbCollage(files, - FileSystem, - 1600, - 900, - ApplicationPaths); + return Task.FromResult(stream); } - protected Task GetSquareCollage(List items) + private IEnumerable GetStripCollageImagePaths(IEnumerable items) { - var files = items + return items .Select(i => i.GetImagePath(ImageType.Primary) ?? i.GetImagePath(ImageType.Thumb)) - .Where(i => !string.IsNullOrWhiteSpace(i)) - .ToList(); + .Where(i => !string.IsNullOrWhiteSpace(i)); + } + + protected Task GetSquareCollage(IHasImages primaryItem, List items) + { + var stream = new StripCollageBuilder(ApplicationPaths).BuildSquareCollage(GetStripCollageImagePaths(items), primaryItem.Name, 800, 800); - return DynamicImageHelpers.GetSquareCollage(files, - FileSystem, - 800, ApplicationPaths); + return Task.FromResult(stream); } public string Name @@ -176,8 +172,8 @@ namespace MediaBrowser.Server.Implementations.Photos } return imageType == ImageType.Thumb ? - await GetThumbCollage(itemsWithImages).ConfigureAwait(false) : - await GetSquareCollage(itemsWithImages).ConfigureAwait(false); + await GetThumbCollage(item, itemsWithImages).ConfigureAwait(false) : + await GetSquareCollage(item, itemsWithImages).ConfigureAwait(false); } public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date) diff --git a/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs b/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs index d6a94210cf..84bab19752 100644 --- a/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs +++ b/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs @@ -248,8 +248,7 @@ namespace MediaBrowser.Server.Implementations.UserViews return null; } - var stream = new StripCollageBuilder(ApplicationPaths).BuildThumbCollage(GetStripCollageImagePaths(itemsWithImages, view.ViewType), item.Name, 960, 540); - return stream; + return new StripCollageBuilder(ApplicationPaths).BuildThumbCollage(GetStripCollageImagePaths(itemsWithImages, view.ViewType), item.Name, 960, 540); } return await base.CreateImageAsync(item, itemsWithImages, imageType, imageIndex); diff --git a/MediaBrowser.Server.Implementations/UserViews/StripCollageBuilder.cs b/MediaBrowser.Server.Implementations/UserViews/StripCollageBuilder.cs index 0bf4d8e4ac..10173c9f89 100644 --- a/MediaBrowser.Server.Implementations/UserViews/StripCollageBuilder.cs +++ b/MediaBrowser.Server.Implementations/UserViews/StripCollageBuilder.cs @@ -18,6 +18,14 @@ namespace MediaBrowser.Server.Implementations.UserViews _appPaths = appPaths; } + public Stream BuildSquareCollage(IEnumerable paths, string text, int width, int height) + { + using (var wand = BuildSquareCollageWand(paths, width, height)) + { + return DynamicImageHelpers.GetStream(wand, _appPaths); + } + } + public Stream BuildThumbCollage(IEnumerable paths, string text, int width, int height) { using (var wand = BuildThumbCollageWand(paths, width, height)) @@ -172,6 +180,62 @@ namespace MediaBrowser.Server.Implementations.UserViews } } + private MagickWand BuildSquareCollageWand(IEnumerable paths, int width, int height) + { + var inputPaths = ProjectPaths(paths, 4); + using (var wandImages = new MagickWand(inputPaths)) + { + var wand = new MagickWand(width, height); + wand.OpenImage("gradient:#111111-#111111"); + using (var draw = new DrawingWand()) + { + var iSlice = Convert.ToInt32(width * 0.2333333334); + int iTrans = Convert.ToInt32(height * .25); + int iHeight = Convert.ToInt32(height * .65); + var horizontalImagePadding = Convert.ToInt32(width * 0.0125); + + foreach (var element in wandImages.ImageList) + { + int iWidth = (int)Math.Abs(iHeight * element.Width / element.Height); + element.Gravity = GravityType.CenterGravity; + element.BackgroundColor = ColorName.Black; + element.ResizeImage(iWidth, iHeight, FilterTypes.LanczosFilter); + int ix = (int)Math.Abs((iWidth - iSlice) / 2); + element.CropImage(iSlice, iHeight, ix, 0); + + element.ExtentImage(iSlice, iHeight, 0 - horizontalImagePadding, 0); + } + + wandImages.SetFirstIterator(); + using (var wandList = wandImages.AppendImages()) + { + wandList.CurrentImage.TrimImage(1); + using (var mwr = wandList.CloneMagickWand()) + { + mwr.CurrentImage.ResizeImage(wandList.CurrentImage.Width, (wandList.CurrentImage.Height / 2), FilterTypes.LanczosFilter, 1); + mwr.CurrentImage.FlipImage(); + + mwr.CurrentImage.AlphaChannel = AlphaChannelType.DeactivateAlphaChannel; + mwr.CurrentImage.ColorizeImage(ColorName.Black, ColorName.Grey70); + + using (var mwg = new MagickWand(wandList.CurrentImage.Width, iTrans)) + { + mwg.OpenImage("gradient:black-none"); + var verticalSpacing = Convert.ToInt32(height * 0.01111111111111111111111111111111); + mwr.CurrentImage.CompositeImage(mwg, CompositeOperator.CopyOpacityCompositeOp, 0, verticalSpacing); + + wandList.AddImage(mwr); + int ex = (int)(wand.CurrentImage.Width - mwg.CurrentImage.Width) / 2; + wand.CurrentImage.CompositeImage(wandList.AppendImages(true), CompositeOperator.AtopCompositeOp, ex, Convert.ToInt32(height * .05)); + } + } + } + } + + return wand; + } + } + private string MontserratLightFont { get { return PlayedIndicatorDrawer.ExtractFont("MontserratLight.otf", _appPaths); } From 527f4887e91f33009be814a29313891c0827c8a7 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 2 Apr 2015 13:44:44 -0400 Subject: [PATCH 42/52] add new dynamic images --- .../Photos/BaseDynamicImageProvider.cs | 33 ++++-- .../UserViews/DynamicImageProvider.cs | 2 +- .../UserViews/StripCollageBuilder.cs | 105 ++++++++++++++++-- 3 files changed, 123 insertions(+), 17 deletions(-) diff --git a/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs b/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs index 0b5ffc903b..401234ac50 100644 --- a/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs +++ b/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs @@ -3,17 +3,17 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Entities; +using MediaBrowser.Server.Implementations.UserViews; using System; using System.Collections.Generic; -using System.Globalization; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Server.Implementations.UserViews; namespace MediaBrowser.Server.Implementations.Photos { @@ -126,7 +126,7 @@ namespace MediaBrowser.Server.Implementations.Photos protected abstract Task> GetItemsWithImages(IHasImages item); - private const string Version = "5"; + private const string Version = "9"; protected string GetConfigurationCacheKey(List items, string itemName) { var parts = Version + "_" + (itemName ?? string.Empty) + "_" + @@ -137,7 +137,7 @@ namespace MediaBrowser.Server.Implementations.Photos protected Task GetThumbCollage(IHasImages primaryItem, List items) { - var stream = new StripCollageBuilder(ApplicationPaths).BuildThumbCollage(GetStripCollageImagePaths(items), primaryItem.Name, 960, 540); + var stream = new StripCollageBuilder(ApplicationPaths).BuildThumbCollage(GetStripCollageImagePaths(items), 960, 540, true, primaryItem.Name); return Task.FromResult(stream); } @@ -149,9 +149,16 @@ namespace MediaBrowser.Server.Implementations.Photos .Where(i => !string.IsNullOrWhiteSpace(i)); } + protected Task GetPosterCollage(IHasImages primaryItem, List items) + { + var stream = new StripCollageBuilder(ApplicationPaths).BuildSquareCollage(GetStripCollageImagePaths(items), 800, 800, true, primaryItem.Name); + + return Task.FromResult(stream); + } + protected Task GetSquareCollage(IHasImages primaryItem, List items) { - var stream = new StripCollageBuilder(ApplicationPaths).BuildSquareCollage(GetStripCollageImagePaths(items), primaryItem.Name, 800, 800); + var stream = new StripCollageBuilder(ApplicationPaths).BuildSquareCollage(GetStripCollageImagePaths(items), 800, 800, true, primaryItem.Name); return Task.FromResult(stream); } @@ -171,9 +178,19 @@ namespace MediaBrowser.Server.Implementations.Photos return null; } - return imageType == ImageType.Thumb ? - await GetThumbCollage(item, itemsWithImages).ConfigureAwait(false) : - await GetSquareCollage(item, itemsWithImages).ConfigureAwait(false); + if (imageType == ImageType.Thumb) + { + return await GetThumbCollage(item, itemsWithImages).ConfigureAwait(false); + } + + if (imageType == ImageType.Primary) + { + return item is PhotoAlbum || item is Playlist ? + await GetSquareCollage(item, itemsWithImages).ConfigureAwait(false) : + await GetPosterCollage(item, itemsWithImages).ConfigureAwait(false); + } + + throw new ArgumentException("Unexpected image type"); } public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date) diff --git a/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs b/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs index 84bab19752..253bf36a26 100644 --- a/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs +++ b/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs @@ -248,7 +248,7 @@ namespace MediaBrowser.Server.Implementations.UserViews return null; } - return new StripCollageBuilder(ApplicationPaths).BuildThumbCollage(GetStripCollageImagePaths(itemsWithImages, view.ViewType), item.Name, 960, 540); + return new StripCollageBuilder(ApplicationPaths).BuildThumbCollage(GetStripCollageImagePaths(itemsWithImages, view.ViewType), 960, 540, false, item.Name); } return await base.CreateImageAsync(item, itemsWithImages, imageType, imageIndex); diff --git a/MediaBrowser.Server.Implementations/UserViews/StripCollageBuilder.cs b/MediaBrowser.Server.Implementations/UserViews/StripCollageBuilder.cs index 10173c9f89..7dbfbb0156 100644 --- a/MediaBrowser.Server.Implementations/UserViews/StripCollageBuilder.cs +++ b/MediaBrowser.Server.Implementations/UserViews/StripCollageBuilder.cs @@ -18,19 +18,39 @@ namespace MediaBrowser.Server.Implementations.UserViews _appPaths = appPaths; } - public Stream BuildSquareCollage(IEnumerable paths, string text, int width, int height) + public Stream BuildSquareCollage(IEnumerable paths, int width, int height, bool renderWithText, string text) { - using (var wand = BuildSquareCollageWand(paths, width, height)) + if (renderWithText) { - return DynamicImageHelpers.GetStream(wand, _appPaths); + using (var wand = BuildSquareCollageWandWithText(paths, text, width, height)) + { + return DynamicImageHelpers.GetStream(wand, _appPaths); + } + } + else + { + using (var wand = BuildSquareCollageWand(paths, width, height)) + { + return DynamicImageHelpers.GetStream(wand, _appPaths); + } } } - public Stream BuildThumbCollage(IEnumerable paths, string text, int width, int height) + public Stream BuildThumbCollage(IEnumerable paths, int width, int height, bool renderWithText, string text) { - using (var wand = BuildThumbCollageWand(paths, width, height)) + if (renderWithText) + { + using (var wand = BuildThumbCollageWandWithText(paths, text, width, height)) + { + return DynamicImageHelpers.GetStream(wand, _appPaths); + } + } + else { - return DynamicImageHelpers.GetStream(wand, _appPaths); + using (var wand = BuildThumbCollageWand(paths, width, height)) + { + return DynamicImageHelpers.GetStream(wand, _appPaths); + } } } @@ -68,7 +88,7 @@ namespace MediaBrowser.Server.Implementations.UserViews { draw.FillColor = fcolor; draw.Font = MontserratLightFont; - draw.FontSize = 50; + draw.FontSize = 60; draw.FontWeight = FontWeightType.LightStyle; draw.TextAntialias = true; } @@ -192,7 +212,7 @@ namespace MediaBrowser.Server.Implementations.UserViews var iSlice = Convert.ToInt32(width * 0.2333333334); int iTrans = Convert.ToInt32(height * .25); int iHeight = Convert.ToInt32(height * .65); - var horizontalImagePadding = Convert.ToInt32(width * 0.0125); + var horizontalImagePadding = Convert.ToInt32(width * 0.02); foreach (var element in wandImages.ImageList) { @@ -236,6 +256,75 @@ namespace MediaBrowser.Server.Implementations.UserViews } } + private MagickWand BuildSquareCollageWandWithText(IEnumerable paths, string label, int width, int height) + { + var inputPaths = ProjectPaths(paths, 4); + using (var wandImages = new MagickWand(inputPaths)) + { + var wand = new MagickWand(width, height); + wand.OpenImage("gradient:#111111-#111111"); + using (var draw = new DrawingWand()) + { + using (var fcolor = new PixelWand(ColorName.White)) + { + draw.FillColor = fcolor; + draw.Font = MontserratLightFont; + draw.FontSize = 60; + draw.FontWeight = FontWeightType.LightStyle; + draw.TextAntialias = true; + } + + var fontMetrics = wand.QueryFontMetrics(draw, label); + var textContainerY = Convert.ToInt32(height * .165); + wand.CurrentImage.AnnotateImage(draw, (width - fontMetrics.TextWidth) / 2, textContainerY, 0.0, label); + + var iSlice = Convert.ToInt32(width * 0.2333333334); + int iTrans = Convert.ToInt32(height * 0.2); + int iHeight = Convert.ToInt32(height * 0.46296296296296296296296296296296); + var horizontalImagePadding = Convert.ToInt32(width * 0.02); + + foreach (var element in wandImages.ImageList) + { + int iWidth = (int)Math.Abs(iHeight * element.Width / element.Height); + element.Gravity = GravityType.CenterGravity; + element.BackgroundColor = new PixelWand("none", 1); + element.ResizeImage(iWidth, iHeight, FilterTypes.LanczosFilter); + int ix = (int)Math.Abs((iWidth - iSlice) / 2); + element.CropImage(iSlice, iHeight, ix, 0); + + element.ExtentImage(iSlice, iHeight, 0 - horizontalImagePadding, 0); + } + + wandImages.SetFirstIterator(); + using (var wandList = wandImages.AppendImages()) + { + wandList.CurrentImage.TrimImage(1); + using (var mwr = wandList.CloneMagickWand()) + { + mwr.CurrentImage.ResizeImage(wandList.CurrentImage.Width, (wandList.CurrentImage.Height / 2), FilterTypes.LanczosFilter, 1); + mwr.CurrentImage.FlipImage(); + + mwr.CurrentImage.AlphaChannel = AlphaChannelType.DeactivateAlphaChannel; + mwr.CurrentImage.ColorizeImage(ColorName.Black, ColorName.Grey60); + + using (var mwg = new MagickWand(wandList.CurrentImage.Width, iTrans)) + { + mwg.OpenImage("gradient:black-none"); + var verticalSpacing = Convert.ToInt32(height * 0.01111111111111111111111111111111); + mwr.CurrentImage.CompositeImage(mwg, CompositeOperator.DstInCompositeOp, 0, verticalSpacing); + + wandList.AddImage(mwr); + int ex = (int)(wand.CurrentImage.Width - mwg.CurrentImage.Width) / 2; + wand.CurrentImage.CompositeImage(wandList.AppendImages(true), CompositeOperator.AtopCompositeOp, ex, Convert.ToInt32(height * 0.26851851851851851851851851851852)); + } + } + } + } + + return wand; + } + } + private string MontserratLightFont { get { return PlayedIndicatorDrawer.ExtractFont("MontserratLight.otf", _appPaths); } From c9c5a9345083c6af1a1ce3733fbc79f385cce0ea Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 2 Apr 2015 17:01:42 -0400 Subject: [PATCH 43/52] add poster dynamic images --- .../Photos/BaseDynamicImageProvider.cs | 4 +- .../UserViews/StripCollageBuilder.cs | 160 ++++++++++++++++-- .../FFMpeg/FFMpegDownloader.cs | 9 +- 3 files changed, 157 insertions(+), 16 deletions(-) diff --git a/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs b/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs index 401234ac50..0909dfec99 100644 --- a/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs +++ b/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs @@ -126,7 +126,7 @@ namespace MediaBrowser.Server.Implementations.Photos protected abstract Task> GetItemsWithImages(IHasImages item); - private const string Version = "9"; + private const string Version = "15"; protected string GetConfigurationCacheKey(List items, string itemName) { var parts = Version + "_" + (itemName ?? string.Empty) + "_" + @@ -151,7 +151,7 @@ namespace MediaBrowser.Server.Implementations.Photos protected Task GetPosterCollage(IHasImages primaryItem, List items) { - var stream = new StripCollageBuilder(ApplicationPaths).BuildSquareCollage(GetStripCollageImagePaths(items), 800, 800, true, primaryItem.Name); + var stream = new StripCollageBuilder(ApplicationPaths).BuildPosterCollage(GetStripCollageImagePaths(items), 600, 900, true, primaryItem.Name); return Task.FromResult(stream); } diff --git a/MediaBrowser.Server.Implementations/UserViews/StripCollageBuilder.cs b/MediaBrowser.Server.Implementations/UserViews/StripCollageBuilder.cs index 7dbfbb0156..de9587286f 100644 --- a/MediaBrowser.Server.Implementations/UserViews/StripCollageBuilder.cs +++ b/MediaBrowser.Server.Implementations/UserViews/StripCollageBuilder.cs @@ -18,22 +18,34 @@ namespace MediaBrowser.Server.Implementations.UserViews _appPaths = appPaths; } - public Stream BuildSquareCollage(IEnumerable paths, int width, int height, bool renderWithText, string text) + public Stream BuildPosterCollage(IEnumerable paths, int width, int height, bool renderWithText, string text) { if (renderWithText) { - using (var wand = BuildSquareCollageWandWithText(paths, text, width, height)) + using (var wand = BuildPosterCollageWandWithText(paths, text, width, height)) { return DynamicImageHelpers.GetStream(wand, _appPaths); } } - else + using (var wand = BuildPosterCollageWand(paths, width, height)) + { + return DynamicImageHelpers.GetStream(wand, _appPaths); + } + } + + public Stream BuildSquareCollage(IEnumerable paths, int width, int height, bool renderWithText, string text) + { + if (renderWithText) { - using (var wand = BuildSquareCollageWand(paths, width, height)) + using (var wand = BuildSquareCollageWandWithText(paths, text, width, height)) { return DynamicImageHelpers.GetStream(wand, _appPaths); } } + using (var wand = BuildSquareCollageWand(paths, width, height)) + { + return DynamicImageHelpers.GetStream(wand, _appPaths); + } } public Stream BuildThumbCollage(IEnumerable paths, int width, int height, bool renderWithText, string text) @@ -45,12 +57,9 @@ namespace MediaBrowser.Server.Implementations.UserViews return DynamicImageHelpers.GetStream(wand, _appPaths); } } - else + using (var wand = BuildThumbCollageWand(paths, width, height)) { - using (var wand = BuildThumbCollageWand(paths, width, height)) - { - return DynamicImageHelpers.GetStream(wand, _appPaths); - } + return DynamicImageHelpers.GetStream(wand, _appPaths); } } @@ -144,6 +153,131 @@ namespace MediaBrowser.Server.Implementations.UserViews } } + private MagickWand BuildPosterCollageWand(IEnumerable paths, int width, int height) + { + var inputPaths = ProjectPaths(paths, 3); + using (var wandImages = new MagickWand(inputPaths)) + { + var wand = new MagickWand(width, height); + wand.OpenImage("gradient:#111111-#111111"); + using (var draw = new DrawingWand()) + { + var iSlice = Convert.ToInt32(width * .3); + int iTrans = Convert.ToInt32(height * .25); + int iHeight = Convert.ToInt32(height * .65); + var horizontalImagePadding = Convert.ToInt32(width * 0.025); + + foreach (var element in wandImages.ImageList) + { + int iWidth = (int)Math.Abs(iHeight * element.Width / element.Height); + element.Gravity = GravityType.CenterGravity; + element.BackgroundColor = ColorName.Black; + element.ResizeImage(iWidth, iHeight, FilterTypes.LanczosFilter); + int ix = (int)Math.Abs((iWidth - iSlice) / 2); + element.CropImage(iSlice, iHeight, ix, 0); + + element.ExtentImage(iSlice, iHeight, 0 - horizontalImagePadding, 0); + } + + wandImages.SetFirstIterator(); + using (var wandList = wandImages.AppendImages()) + { + wandList.CurrentImage.TrimImage(1); + using (var mwr = wandList.CloneMagickWand()) + { + mwr.CurrentImage.ResizeImage(wandList.CurrentImage.Width, (wandList.CurrentImage.Height / 2), FilterTypes.LanczosFilter, 1); + mwr.CurrentImage.FlipImage(); + + mwr.CurrentImage.AlphaChannel = AlphaChannelType.DeactivateAlphaChannel; + mwr.CurrentImage.ColorizeImage(ColorName.Black, ColorName.Grey70); + + using (var mwg = new MagickWand(wandList.CurrentImage.Width, iTrans)) + { + mwg.OpenImage("gradient:black-none"); + var verticalSpacing = Convert.ToInt32(height * 0.01111111111111111111111111111111); + mwr.CurrentImage.CompositeImage(mwg, CompositeOperator.CopyOpacityCompositeOp, 0, verticalSpacing); + + wandList.AddImage(mwr); + int ex = (int)(wand.CurrentImage.Width - mwg.CurrentImage.Width) / 2; + wand.CurrentImage.CompositeImage(wandList.AppendImages(true), CompositeOperator.AtopCompositeOp, ex, Convert.ToInt32(height * .05)); + } + } + } + } + + return wand; + } + } + + private MagickWand BuildPosterCollageWandWithText(IEnumerable paths, string label, int width, int height) + { + var inputPaths = ProjectPaths(paths, 3); + using (var wandImages = new MagickWand(inputPaths)) + { + var wand = new MagickWand(width, height); + wand.OpenImage("gradient:#111111-#111111"); + using (var draw = new DrawingWand()) + { + using (var fcolor = new PixelWand(ColorName.White)) + { + draw.FillColor = fcolor; + draw.Font = MontserratLightFont; + draw.FontSize = 60; + draw.FontWeight = FontWeightType.LightStyle; + draw.TextAntialias = true; + } + + var fontMetrics = wand.QueryFontMetrics(draw, label); + var textContainerY = Convert.ToInt32(height * .165); + wand.CurrentImage.AnnotateImage(draw, (width - fontMetrics.TextWidth) / 2, textContainerY, 0.0, label); + + var iSlice = Convert.ToInt32(width * .3); + int iTrans = Convert.ToInt32(height * 0.2); + int iHeight = Convert.ToInt32(height * 0.46296296296296296296296296296296); + var horizontalImagePadding = Convert.ToInt32(width * 0.025); + + foreach (var element in wandImages.ImageList) + { + int iWidth = (int)Math.Abs(iHeight * element.Width / element.Height); + element.Gravity = GravityType.CenterGravity; + element.BackgroundColor = new PixelWand("none", 1); + element.ResizeImage(iWidth, iHeight, FilterTypes.LanczosFilter); + int ix = (int)Math.Abs((iWidth - iSlice) / 2); + element.CropImage(iSlice, iHeight, ix, 0); + + element.ExtentImage(iSlice, iHeight, 0 - horizontalImagePadding, 0); + } + + wandImages.SetFirstIterator(); + using (var wandList = wandImages.AppendImages()) + { + wandList.CurrentImage.TrimImage(1); + using (var mwr = wandList.CloneMagickWand()) + { + mwr.CurrentImage.ResizeImage(wandList.CurrentImage.Width, (wandList.CurrentImage.Height / 2), FilterTypes.LanczosFilter, 1); + mwr.CurrentImage.FlipImage(); + + mwr.CurrentImage.AlphaChannel = AlphaChannelType.DeactivateAlphaChannel; + mwr.CurrentImage.ColorizeImage(ColorName.Black, ColorName.Grey60); + + using (var mwg = new MagickWand(wandList.CurrentImage.Width, iTrans)) + { + mwg.OpenImage("gradient:black-none"); + var verticalSpacing = Convert.ToInt32(height * 0.01111111111111111111111111111111); + mwr.CurrentImage.CompositeImage(mwg, CompositeOperator.DstInCompositeOp, 0, verticalSpacing); + + wandList.AddImage(mwr); + int ex = (int)(wand.CurrentImage.Width - mwg.CurrentImage.Width) / 2; + wand.CurrentImage.CompositeImage(wandList.AppendImages(true), CompositeOperator.AtopCompositeOp, ex, Convert.ToInt32(height * 0.26851851851851851851851851851852)); + } + } + } + } + + return wand; + } + } + private MagickWand BuildThumbCollageWand(IEnumerable paths, int width, int height) { var inputPaths = ProjectPaths(paths, 8); @@ -209,9 +343,9 @@ namespace MediaBrowser.Server.Implementations.UserViews wand.OpenImage("gradient:#111111-#111111"); using (var draw = new DrawingWand()) { - var iSlice = Convert.ToInt32(width * 0.2333333334); + var iSlice = Convert.ToInt32(width * .225); int iTrans = Convert.ToInt32(height * .25); - int iHeight = Convert.ToInt32(height * .65); + int iHeight = Convert.ToInt32(height * .63); var horizontalImagePadding = Convert.ToInt32(width * 0.02); foreach (var element in wandImages.ImageList) @@ -246,7 +380,7 @@ namespace MediaBrowser.Server.Implementations.UserViews wandList.AddImage(mwr); int ex = (int)(wand.CurrentImage.Width - mwg.CurrentImage.Width) / 2; - wand.CurrentImage.CompositeImage(wandList.AppendImages(true), CompositeOperator.AtopCompositeOp, ex, Convert.ToInt32(height * .05)); + wand.CurrentImage.CompositeImage(wandList.AppendImages(true), CompositeOperator.AtopCompositeOp, ex, Convert.ToInt32(height * .07)); } } } @@ -278,7 +412,7 @@ namespace MediaBrowser.Server.Implementations.UserViews var textContainerY = Convert.ToInt32(height * .165); wand.CurrentImage.AnnotateImage(draw, (width - fontMetrics.TextWidth) / 2, textContainerY, 0.0, label); - var iSlice = Convert.ToInt32(width * 0.2333333334); + var iSlice = Convert.ToInt32(width * .225); int iTrans = Convert.ToInt32(height * 0.2); int iHeight = Convert.ToInt32(height * 0.46296296296296296296296296296296); var horizontalImagePadding = Convert.ToInt32(width * 0.02); diff --git a/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegDownloader.cs b/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegDownloader.cs index d4cefdb10a..fe7cd943a2 100644 --- a/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegDownloader.cs +++ b/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegDownloader.cs @@ -202,7 +202,14 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg } } - throw new ApplicationException("Unable to download required components. Please try again later."); + if (downloadinfo.DownloadUrls.Length == 0) + { + throw new ApplicationException("ffmpeg unvailable. Please install it and start the server with two command line arguments: -ffmpeg \"{PATH}\" and -ffprobe \"{PATH}\""); + } + else + { + throw new ApplicationException("Unable to download required components. Please try again later."); + } } private void ExtractFFMpeg(FFMpegDownloadInfo downloadinfo, string tempFile, string targetFolder) From 73c077ce8a5fb4a6be71655e38fde29b92f8b933 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 2 Apr 2015 18:26:39 -0400 Subject: [PATCH 44/52] update default UserPolicy --- MediaBrowser.Model/Users/UserPolicy.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/MediaBrowser.Model/Users/UserPolicy.cs b/MediaBrowser.Model/Users/UserPolicy.cs index 7efc2cf6f6..774d4a0b47 100644 --- a/MediaBrowser.Model/Users/UserPolicy.cs +++ b/MediaBrowser.Model/Users/UserPolicy.cs @@ -63,6 +63,7 @@ namespace MediaBrowser.Model.Users public UserPolicy() { + EnableSync = true; EnableLiveTvManagement = true; EnableMediaPlayback = true; EnableLiveTvAccess = true; From e9de3278bef9e05d8791ed01432696495c87781d Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 2 Apr 2015 22:35:15 -0400 Subject: [PATCH 45/52] hls resume fix --- MediaBrowser.Model/Dlna/StreamInfo.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index a57bc0c0ef..a908c78506 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -198,7 +198,16 @@ namespace MediaBrowser.Model.Dlna list.Add(new NameValuePair("MaxFramerate", item.MaxFramerate.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxFramerate.Value) : string.Empty)); list.Add(new NameValuePair("MaxWidth", item.MaxWidth.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxWidth.Value) : string.Empty)); list.Add(new NameValuePair("MaxHeight", item.MaxHeight.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxHeight.Value) : string.Empty)); - list.Add(new NameValuePair("StartTimeTicks", StringHelper.ToStringCultureInvariant(item.StartPositionTicks))); + + if (StringHelper.EqualsIgnoreCase(item.SubProtocol, "hls")) + { + list.Add(new NameValuePair("StartTimeTicks", string.Empty)); + } + else + { + list.Add(new NameValuePair("StartTimeTicks", StringHelper.ToStringCultureInvariant(item.StartPositionTicks))); + } + list.Add(new NameValuePair("Level", item.VideoLevel.HasValue ? StringHelper.ToStringCultureInvariant(item.VideoLevel.Value) : string.Empty)); list.Add(new NameValuePair("ClientTime", item.IsDirectStream ? string.Empty : DateTime.UtcNow.Ticks.ToString(CultureInfo.InvariantCulture))); From 439e7efecb7f3612493dea864ccfb46c180ae528 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 2 Apr 2015 22:36:15 -0400 Subject: [PATCH 46/52] remove extra trims --- MediaBrowser.Api/Playback/MediaInfoService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs index 24c9d637ad..08c5d56db2 100644 --- a/MediaBrowser.Api/Playback/MediaInfoService.cs +++ b/MediaBrowser.Api/Playback/MediaInfoService.cs @@ -302,7 +302,7 @@ namespace MediaBrowser.Api.Playback if (streamInfo != null && streamInfo.PlayMethod == PlayMethod.Transcode) { streamInfo.StartPositionTicks = startTimeTicks; - mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).TrimStart('-').TrimStart('-'); + mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).TrimStart('-'); mediaSource.TranscodingContainer = streamInfo.Container; mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol; } @@ -324,7 +324,7 @@ namespace MediaBrowser.Api.Playback if (profile.DeliveryMethod == SubtitleDeliveryMethod.External) { - stream.DeliveryUrl = profile.Url.TrimStart('-').TrimStart('-'); + stream.DeliveryUrl = profile.Url.TrimStart('-'); stream.IsExternalUrl = profile.IsExternalUrl; } } From 305398ed9c71ab75a19a20cd7ebcaab5de38892c Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 2 Apr 2015 22:54:40 -0400 Subject: [PATCH 47/52] update dynamic images --- .../Photos/BaseDynamicImageProvider.cs | 2 +- .../UserViews/StripCollageBuilder.cs | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs b/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs index 0909dfec99..603ce87f29 100644 --- a/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs +++ b/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs @@ -126,7 +126,7 @@ namespace MediaBrowser.Server.Implementations.Photos protected abstract Task> GetItemsWithImages(IHasImages item); - private const string Version = "15"; + private const string Version = "17"; protected string GetConfigurationCacheKey(List items, string itemName) { var parts = Version + "_" + (itemName ?? string.Empty) + "_" + diff --git a/MediaBrowser.Server.Implementations/UserViews/StripCollageBuilder.cs b/MediaBrowser.Server.Implementations/UserViews/StripCollageBuilder.cs index de9587286f..a9ac0946ac 100644 --- a/MediaBrowser.Server.Implementations/UserViews/StripCollageBuilder.cs +++ b/MediaBrowser.Server.Implementations/UserViews/StripCollageBuilder.cs @@ -155,17 +155,17 @@ namespace MediaBrowser.Server.Implementations.UserViews private MagickWand BuildPosterCollageWand(IEnumerable paths, int width, int height) { - var inputPaths = ProjectPaths(paths, 3); + var inputPaths = ProjectPaths(paths, 4); using (var wandImages = new MagickWand(inputPaths)) { var wand = new MagickWand(width, height); wand.OpenImage("gradient:#111111-#111111"); using (var draw = new DrawingWand()) { - var iSlice = Convert.ToInt32(width * .3); + var iSlice = Convert.ToInt32(width * 0.225); int iTrans = Convert.ToInt32(height * .25); int iHeight = Convert.ToInt32(height * .65); - var horizontalImagePadding = Convert.ToInt32(width * 0.025); + var horizontalImagePadding = Convert.ToInt32(width * 0.0275); foreach (var element in wandImages.ImageList) { @@ -211,7 +211,7 @@ namespace MediaBrowser.Server.Implementations.UserViews private MagickWand BuildPosterCollageWandWithText(IEnumerable paths, string label, int width, int height) { - var inputPaths = ProjectPaths(paths, 3); + var inputPaths = ProjectPaths(paths, 4); using (var wandImages = new MagickWand(inputPaths)) { var wand = new MagickWand(width, height); @@ -231,10 +231,10 @@ namespace MediaBrowser.Server.Implementations.UserViews var textContainerY = Convert.ToInt32(height * .165); wand.CurrentImage.AnnotateImage(draw, (width - fontMetrics.TextWidth) / 2, textContainerY, 0.0, label); - var iSlice = Convert.ToInt32(width * .3); + var iSlice = Convert.ToInt32(width * 0.225); int iTrans = Convert.ToInt32(height * 0.2); int iHeight = Convert.ToInt32(height * 0.46296296296296296296296296296296); - var horizontalImagePadding = Convert.ToInt32(width * 0.025); + var horizontalImagePadding = Convert.ToInt32(width * 0.0275); foreach (var element in wandImages.ImageList) { From 4655a60d2992b2bb674dda783fbaeb5fba48918f Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 3 Apr 2015 11:50:50 -0400 Subject: [PATCH 48/52] remove copyts from hls --- .../Playback/Hls/DynamicHlsService.cs | 4 +- .../Configuration/UserConfiguration.cs | 1 + .../Channels/ChannelDownloadScheduledTask.cs | 41 ++++++++++--------- 3 files changed, 25 insertions(+), 21 deletions(-) diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index 4f043d3217..cbfadb886d 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -698,7 +698,7 @@ namespace MediaBrowser.Api.Playback.Hls { var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d.ts"; - return string.Format("{0} {1} -map_metadata -1 -threads {2} {3} {4} -copyts -flags -global_header -sc_threshold 0 {5} -f segment -segment_time {6} -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"", + return string.Format("{0} {1} -map_metadata -1 -threads {2} {3} {4} -flags -global_header -sc_threshold 0 {5} -f segment -segment_time {6} -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"", inputModifier, GetInputArgument(state), threads, @@ -712,7 +712,7 @@ namespace MediaBrowser.Api.Playback.Hls ).Trim(); } - return string.Format("{0} {1} -map_metadata -1 -threads {2} {3} {4} -copyts -flags -global_header -sc_threshold 0 {5} -hls_time {6} -start_number {7} -hls_list_size {8} -y \"{9}\"", + return string.Format("{0} {1} -map_metadata -1 -threads {2} {3} {4} -flags -global_header -sc_threshold 0 {5} -hls_time {6} -start_number {7} -hls_list_size {8} -y \"{9}\"", inputModifier, GetInputArgument(state), threads, diff --git a/MediaBrowser.Model/Configuration/UserConfiguration.cs b/MediaBrowser.Model/Configuration/UserConfiguration.cs index 98641636ae..7390af0881 100644 --- a/MediaBrowser.Model/Configuration/UserConfiguration.cs +++ b/MediaBrowser.Model/Configuration/UserConfiguration.cs @@ -61,6 +61,7 @@ namespace MediaBrowser.Model.Configuration /// public UserConfiguration() { + HidePlayedInLatest = true; PlayDefaultAudioTrack = true; LatestItemsExcludes = new string[] { }; diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs b/MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs index 980c3f31b7..6514893477 100644 --- a/MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs +++ b/MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs @@ -169,29 +169,32 @@ namespace MediaBrowser.Server.Implementations.Channels foreach (var item in result.Items) { - var channelItem = (IChannelMediaItem)item; + var channelItem = item as IChannelMediaItem; - var channelFeatures = _manager.GetChannelFeatures(channelItem.ChannelId); - - if (channelFeatures.SupportsContentDownloading) + if (channelItem != null) { - if (options.DownloadingChannels.Contains(channelItem.ChannelId)) + var channelFeatures = _manager.GetChannelFeatures(channelItem.ChannelId); + + if (channelFeatures.SupportsContentDownloading) { - try - { - await DownloadChannelItem(channelItem, options, cancellationToken, path); - } - catch (OperationCanceledException) - { - break; - } - catch (ChannelDownloadException) - { - // Logged at lower levels - } - catch (Exception ex) + if (options.DownloadingChannels.Contains(channelItem.ChannelId)) { - _logger.ErrorException("Error downloading channel content for {0}", ex, item.Name); + try + { + await DownloadChannelItem(channelItem, options, cancellationToken, path); + } + catch (OperationCanceledException) + { + break; + } + catch (ChannelDownloadException) + { + // Logged at lower levels + } + catch (Exception ex) + { + _logger.ErrorException("Error downloading channel content for {0}", ex, item.Name); + } } } } From 9924019f57a120eafc721bd070d678fb2bd62168 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 3 Apr 2015 11:52:49 -0400 Subject: [PATCH 49/52] restore model properties --- MediaBrowser.Api/Playback/Hls/BaseHlsService.cs | 1 - MediaBrowser.Model/Querying/ItemQuery.cs | 10 ++++++++++ .../HttpServer/ThrottledStream.cs | 12 ------------ .../Localization/JavaScript/javascript.json | 6 ++++++ .../Localization/Server/server.json | 2 +- 5 files changed, 17 insertions(+), 14 deletions(-) diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index 657a97ac15..701516b48c 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -3,7 +3,6 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Extensions; using MediaBrowser.Model.IO; diff --git a/MediaBrowser.Model/Querying/ItemQuery.cs b/MediaBrowser.Model/Querying/ItemQuery.cs index 0cdf5ca7ab..5a88c0d43e 100644 --- a/MediaBrowser.Model/Querying/ItemQuery.cs +++ b/MediaBrowser.Model/Querying/ItemQuery.cs @@ -281,6 +281,13 @@ namespace MediaBrowser.Model.Querying public int? ImageTypeLimit { get; set; } public ImageType[] EnableImageTypes { get; set; } + [Obsolete] + public string[] Artists { get; set; } + [Obsolete] + public string[] Studios { get; set; } + [Obsolete] + public string Person { get; set; } + /// /// Initializes a new instance of the class. /// @@ -299,6 +306,9 @@ namespace MediaBrowser.Model.Querying VideoTypes = new VideoType[] { }; + Artists = new string[] { }; + Studios = new string[] { }; + Genres = new string[] { }; StudioIds = new string[] { }; IncludeItemTypes = new string[] { }; diff --git a/MediaBrowser.Server.Implementations/HttpServer/ThrottledStream.cs b/MediaBrowser.Server.Implementations/HttpServer/ThrottledStream.cs index 4bde30dac3..1c01fa9e04 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/ThrottledStream.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/ThrottledStream.cs @@ -15,8 +15,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer /// public const long Infinite = 0; - public Func ThrottleCallback { get; set; } - #region Private members /// /// The base stream. @@ -293,16 +291,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer return false; } - if (ThrottleCallback != null) - { - var val = ThrottleCallback(_maximumBytesPerSecond, _bytesWritten); - - if (val == 0) - { - return false; - } - } - return true; } diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json index ae20fafbab..9c53eb92e0 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json @@ -35,8 +35,11 @@ "HeaderConfirmation": "Confirmation", "MessageKeyUpdated": "Thank you. Your supporter key has been updated.", "MessageKeyRemoved": "Thank you. Your supporter key has been removed.", + "HeaderSupportTheTeam": "Support the Emby Team", + "TextEnjoyBonusFeatures": "Enjoy Bonus Features", "TitleLiveTV": "Live TV", "TitleSync": "Sync", + "ButtonDonate": "Donate", "ErrorLaunchingChromecast": "There was an error launching chromecast. Please ensure your device is connected to your wireless network.", "MessageErrorLoadingSupporterInfo": "There was an error loading supporter information. Please try again later.", "MessageLinkYourSupporterKey": "Link your supporter key with up to {0} Emby Connect members to enjoy free access to the following apps:", @@ -175,6 +178,9 @@ "OptionThursday": "Thursday", "OptionFriday": "Friday", "OptionSaturday": "Saturday", + "OptionEveryday": "Every day", + "OptionWeekend": "Weekends", + "OptionWeekday": "Weekdays", "HeaderConfirmDeletion": "Confirm Deletion", "MessageConfirmPathSubstitutionDeletion": "Are you sure you wish to delete this path substitution?", "LiveTvUpdateAvailable": "(Update available)", diff --git a/MediaBrowser.Server.Implementations/Localization/Server/server.json b/MediaBrowser.Server.Implementations/Localization/Server/server.json index d27403dda5..3f9a36560d 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/server.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/server.json @@ -913,8 +913,8 @@ "OptionDefaultSort": "Default", "OptionCommunityMostWatchedSort": "Most Watched", "TabNextUp": "Next Up", + "PlaceholderUsername": "Username", "HeaderBecomeProjectSupporter": "Become an Emby Supporter", - "TextEnjoyBonusFeatures": "Enjoy Bonus Features", "MessageNoMovieSuggestionsAvailable": "No movie suggestions are currently available. Start watching and rating your movies, and then come back to view your recommendations.", "MessageNoCollectionsAvailable": "Collections allow you to enjoy personalized groupings of Movies, Series, Albums, Books and Games. Click the + button to start creating Collections.", "MessageNoPlaylistsAvailable": "Playlists allow you to create lists of content to play consecutively at a time. To add items to playlists, right click or tap and hold, then select Add to Playlist.", From 165423697892764575ed2908c9b66534d920ae81 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 3 Apr 2015 11:59:56 -0400 Subject: [PATCH 50/52] activate item counts --- MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs | 3 +-- MediaBrowser.Server.Implementations/Dto/DtoService.cs | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs index 609c1048f2..b2364ce3c6 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs +++ b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs @@ -142,7 +142,7 @@ namespace MediaBrowser.Api.UserLibrary } IEnumerable>> tuples; - if (dtoOptions.Fields.Contains(ItemFields.ItemCounts) || true) + if (dtoOptions.Fields.Contains(ItemFields.ItemCounts)) { tuples = ibnItems.Select(i => new Tuple>(i, i.GetTaggedItems(libraryItems).ToList())); } @@ -177,7 +177,6 @@ namespace MediaBrowser.Api.UserLibrary return true; } - return true; return options.Fields.Contains(ItemFields.ItemCounts); } diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index dc6f4a5250..364a47945b 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -97,7 +97,7 @@ namespace MediaBrowser.Server.Implementations.Dto if (byName != null && !(item is LiveTvChannel)) { - //if (options.Fields.Contains(ItemFields.ItemCounts)) + if (options.Fields.Contains(ItemFields.ItemCounts)) { var itemFilter = byName.GetItemFilter(); @@ -125,7 +125,7 @@ namespace MediaBrowser.Server.Implementations.Dto if (byName != null && !(item is LiveTvChannel)) { - //if (options.Fields.Contains(ItemFields.ItemCounts)) + if (options.Fields.Contains(ItemFields.ItemCounts)) { var itemFilter = byName.GetItemFilter(); @@ -317,7 +317,7 @@ namespace MediaBrowser.Server.Implementations.Dto { var dto = GetBaseItemDtoInternal(item, options, user); - //if (options.Fields.Contains(ItemFields.ItemCounts)) + if (options.Fields.Contains(ItemFields.ItemCounts)) { SetItemByNameInfo(item, dto, taggedItems, user); } From 0481fef03bada66c466fd9e7e4c3c5e5a1ed30ad Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 3 Apr 2015 12:31:56 -0400 Subject: [PATCH 51/52] add sports to suggested tv --- MediaBrowser.Api/LiveTv/LiveTvService.cs | 10 +++- MediaBrowser.Model/LiveTv/ProgramQuery.cs | 6 +++ .../LiveTv/RecommendedProgramQuery.cs | 5 ++ MediaBrowser.Model/Querying/ItemSortBy.cs | 1 + .../LiveTv/LiveTvManager.cs | 17 +++++++ .../Localization/Server/server.json | 3 +- ...MediaBrowser.Server.Implementations.csproj | 1 + .../Sorting/StartDateComparer.cs | 47 +++++++++++++++++++ 8 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 MediaBrowser.Server.Implementations/Sorting/StartDateComparer.cs diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index 24c91e172f..bb6f74f364 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -186,6 +186,9 @@ namespace MediaBrowser.Api.LiveTv [ApiMember(Name = "IsMovie", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] public bool? IsMovie { get; set; } + [ApiMember(Name = "IsSports", Description = "Optional filter for sports.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] + public bool? IsSports { get; set; } + [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] public int? StartIndex { get; set; } @@ -218,6 +221,9 @@ namespace MediaBrowser.Api.LiveTv [ApiMember(Name = "HasAired", Description = "Optional. Filter by programs that have completed airing, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] public bool? HasAired { get; set; } + [ApiMember(Name = "IsSports", Description = "Optional filter for sports.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] + public bool? IsSports { get; set; } + [ApiMember(Name = "IsMovie", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] public bool? IsMovie { get; set; } } @@ -422,6 +428,7 @@ namespace MediaBrowser.Api.LiveTv query.SortBy = (request.SortBy ?? String.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); query.SortOrder = request.SortOrder; query.IsMovie = request.IsMovie; + query.IsSports = request.IsSports; query.Genres = (request.Genres ?? String.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); var result = await _liveTvManager.GetPrograms(query, CancellationToken.None).ConfigureAwait(false); @@ -437,7 +444,8 @@ namespace MediaBrowser.Api.LiveTv IsAiring = request.IsAiring, Limit = request.Limit, HasAired = request.HasAired, - IsMovie = request.IsMovie + IsMovie = request.IsMovie, + IsSports = request.IsSports }; var result = await _liveTvManager.GetRecommendedPrograms(query, CancellationToken.None).ConfigureAwait(false); diff --git a/MediaBrowser.Model/LiveTv/ProgramQuery.cs b/MediaBrowser.Model/LiveTv/ProgramQuery.cs index bbd396c33f..c19ba54bd1 100644 --- a/MediaBrowser.Model/LiveTv/ProgramQuery.cs +++ b/MediaBrowser.Model/LiveTv/ProgramQuery.cs @@ -53,6 +53,12 @@ namespace MediaBrowser.Model.LiveTv /// If set to null, all programs will be returned public bool? IsMovie { get; set; } + /// + /// Gets or sets a value indicating whether this instance is sports. + /// + /// null if [is sports] contains no value, true if [is sports]; otherwise, false. + public bool? IsSports { get; set; } + /// /// Skips over a given number of items within the results. Use for paging. /// diff --git a/MediaBrowser.Model/LiveTv/RecommendedProgramQuery.cs b/MediaBrowser.Model/LiveTv/RecommendedProgramQuery.cs index 9ba8e0e5fc..4a8ae2365b 100644 --- a/MediaBrowser.Model/LiveTv/RecommendedProgramQuery.cs +++ b/MediaBrowser.Model/LiveTv/RecommendedProgramQuery.cs @@ -31,5 +31,10 @@ /// /// null if [is movie] contains no value, true if [is movie]; otherwise, false. public bool? IsMovie { get; set; } + /// + /// Gets or sets a value indicating whether this instance is sports. + /// + /// null if [is sports] contains no value, true if [is sports]; otherwise, false. + public bool? IsSports { get; set; } } } \ No newline at end of file diff --git a/MediaBrowser.Model/Querying/ItemSortBy.cs b/MediaBrowser.Model/Querying/ItemSortBy.cs index fcc7e39a19..9c2926b542 100644 --- a/MediaBrowser.Model/Querying/ItemSortBy.cs +++ b/MediaBrowser.Model/Querying/ItemSortBy.cs @@ -43,6 +43,7 @@ namespace MediaBrowser.Model.Querying /// The premiere date /// public const string PremiereDate = "PremiereDate"; + public const string StartDate = "StartDate"; /// /// The sort name /// diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index a39781d6a3..cb9bb77111 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -761,6 +761,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv programs = programs.Where(p => p.IsMovie == query.IsMovie); } + if (query.IsSports.HasValue) + { + programs = programs.Where(p => p.IsSports == query.IsSports); + } + programs = _libraryManager.Sort(programs, user, query.SortBy, query.SortOrder ?? SortOrder.Ascending) .Cast(); @@ -826,6 +831,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv programs = programs.Where(p => p.IsMovie == query.IsMovie.Value); } + if (query.IsSports.HasValue) + { + programs = programs.Where(p => p.IsSports == query.IsSports.Value); + } + var programList = programs.ToList(); var genres = programList.SelectMany(i => i.Genres) @@ -996,6 +1006,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv innerProgress = new ActionableProgress(); innerProgress.RegisterAction(p => progress.Report(90 + (p * .1))); await CleanDatabaseInternal(progress, cancellationToken).ConfigureAwait(false); + + foreach (var program in _programs.Values + .Where(i => (i.StartDate - DateTime.UtcNow).TotalDays <= 1) + .ToList()) + { + RefreshIfNeeded(program); + } } finally { diff --git a/MediaBrowser.Server.Implementations/Localization/Server/server.json b/MediaBrowser.Server.Implementations/Localization/Server/server.json index 3f9a36560d..835e9b3beb 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/server.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/server.json @@ -913,7 +913,7 @@ "OptionDefaultSort": "Default", "OptionCommunityMostWatchedSort": "Most Watched", "TabNextUp": "Next Up", - "PlaceholderUsername": "Username", + "PlaceholderUsername": "Username", "HeaderBecomeProjectSupporter": "Become an Emby Supporter", "MessageNoMovieSuggestionsAvailable": "No movie suggestions are currently available. Start watching and rating your movies, and then come back to view your recommendations.", "MessageNoCollectionsAvailable": "Collections allow you to enjoy personalized groupings of Movies, Series, Albums, Books and Games. Click the + button to start creating Collections.", @@ -1399,6 +1399,7 @@ "LabelEnableInternetMetadataForTvPrograms": "Download internet metadata for:", "OptionTVMovies": "TV Movies", "HeaderUpcomingMovies": "Upcoming Movies", + "HeaderUpcomingSports": "Upcoming Sports", "HeaderUpcomingPrograms": "Upcoming Programs", "ButtonMoreItems": "More...", "LabelShowLibraryTileNames": "Show library tile names", diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index db2397d2f7..dd770b0c89 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -233,6 +233,7 @@ + diff --git a/MediaBrowser.Server.Implementations/Sorting/StartDateComparer.cs b/MediaBrowser.Server.Implementations/Sorting/StartDateComparer.cs new file mode 100644 index 0000000000..7e6f24ec1c --- /dev/null +++ b/MediaBrowser.Server.Implementations/Sorting/StartDateComparer.cs @@ -0,0 +1,47 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Querying; +using System; + +namespace MediaBrowser.Server.Implementations.Sorting +{ + public class StartDateComparer : IBaseItemComparer + { + /// + /// Compares the specified x. + /// + /// The x. + /// The y. + /// System.Int32. + public int Compare(BaseItem x, BaseItem y) + { + return GetDate(x).CompareTo(GetDate(y)); + } + + /// + /// Gets the date. + /// + /// The x. + /// DateTime. + private DateTime GetDate(BaseItem x) + { + var hasStartDate = x as LiveTvProgram; + + if (hasStartDate != null) + { + return hasStartDate.StartDate; + } + return DateTime.MinValue; + } + + /// + /// Gets the name. + /// + /// The name. + public string Name + { + get { return ItemSortBy.StartDate; } + } + } +} From ef505c8e9e2b8f348aeaa89be6bc446014b72996 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 3 Apr 2015 12:58:12 -0400 Subject: [PATCH 52/52] update images --- .../Photos/BaseDynamicImageProvider.cs | 2 +- .../UserViews/DynamicImageProvider.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs b/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs index 603ce87f29..79a1181cab 100644 --- a/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs +++ b/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs @@ -126,7 +126,7 @@ namespace MediaBrowser.Server.Implementations.Photos protected abstract Task> GetItemsWithImages(IHasImages item); - private const string Version = "17"; + private const string Version = "18"; protected string GetConfigurationCacheKey(List items, string itemName) { var parts = Version + "_" + (itemName ?? string.Empty) + "_" + diff --git a/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs b/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs index 253bf36a26..7bcbbd6a8a 100644 --- a/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs +++ b/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs @@ -107,7 +107,7 @@ namespace MediaBrowser.Server.Implementations.UserViews User = _userManager.GetUserById(view.UserId.Value), CollapseBoxSetItems = false, Recursive = recursive, - ExcludeItemTypes = new[] { "UserView", "CollectionFolder" } + ExcludeItemTypes = new[] { "UserView", "CollectionFolder", "Playlist" } }).ConfigureAwait(false);