diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index b530a1e8bc..50e7319b9e 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -1,5 +1,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Querying; using ServiceStack; @@ -205,6 +206,11 @@ namespace MediaBrowser.Api.LiveTv [Api(Description = "Gets live tv series timers")] public class GetSeriesTimers : IReturn> { + [ApiMember(Name = "SortBy", Description = "Optional. Sort by SortName or Priority", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")] + public string SortBy { get; set; } + + [ApiMember(Name = "SortOrder", Description = "Optional. Sort in Ascending or Descending order", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")] + public SortOrder SortOrder { get; set; } } [Route("/LiveTv/SeriesTimers/{Id}", "DELETE")] @@ -296,7 +302,7 @@ namespace MediaBrowser.Api.LiveTv { var query = new ProgramQuery { - ChannelIdList = (request.ChannelIds ?? string.Empty).Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries).ToArray(), + ChannelIdList = (request.ChannelIds ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToArray(), UserId = request.UserId }; @@ -400,6 +406,8 @@ namespace MediaBrowser.Api.LiveTv { var result = _liveTvManager.GetSeriesTimers(new SeriesTimerQuery { + SortOrder = request.SortOrder, + SortBy = request.SortBy }, CancellationToken.None).Result; diff --git a/MediaBrowser.Controller/MediaInfo/MediaEncoderHelpers.cs b/MediaBrowser.Controller/MediaInfo/MediaEncoderHelpers.cs index 6b8276eb10..261454f6d7 100644 --- a/MediaBrowser.Controller/MediaInfo/MediaEncoderHelpers.cs +++ b/MediaBrowser.Controller/MediaInfo/MediaEncoderHelpers.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using MediaBrowser.Common.MediaInfo; @@ -117,5 +118,239 @@ namespace MediaBrowser.Controller.MediaInfo return type; } + + public static IEnumerable GetMediaStreams(MediaInfoResult data) + { + var internalStreams = data.streams ?? new MediaStreamInfo[] { }; + + return internalStreams.Select(s => GetMediaStream(s, data.format)) + .Where(i => i != null); + } + + private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); + + /// + /// Converts ffprobe stream info to our MediaStream class + /// + /// The stream info. + /// The format info. + /// MediaStream. + private static MediaStream GetMediaStream(MediaStreamInfo streamInfo, MediaFormatInfo formatInfo) + { + var stream = new MediaStream + { + Codec = streamInfo.codec_name, + Profile = streamInfo.profile, + Level = streamInfo.level, + Index = streamInfo.index + }; + + if (streamInfo.tags != null) + { + stream.Language = GetDictionaryValue(streamInfo.tags, "language"); + } + + if (string.Equals(streamInfo.codec_type, "audio", StringComparison.OrdinalIgnoreCase)) + { + stream.Type = MediaStreamType.Audio; + + stream.Channels = streamInfo.channels; + + if (!string.IsNullOrEmpty(streamInfo.sample_rate)) + { + stream.SampleRate = int.Parse(streamInfo.sample_rate, UsCulture); + } + + stream.ChannelLayout = ParseChannelLayout(streamInfo.channel_layout); + } + else if (string.Equals(streamInfo.codec_type, "subtitle", StringComparison.OrdinalIgnoreCase)) + { + stream.Type = MediaStreamType.Subtitle; + } + else if (string.Equals(streamInfo.codec_type, "video", StringComparison.OrdinalIgnoreCase)) + { + stream.Type = MediaStreamType.Video; + + stream.Width = streamInfo.width; + stream.Height = streamInfo.height; + stream.AspectRatio = GetAspectRatio(streamInfo); + + stream.AverageFrameRate = GetFrameRate(streamInfo.avg_frame_rate); + stream.RealFrameRate = GetFrameRate(streamInfo.r_frame_rate); + } + else + { + return null; + } + + // Get stream bitrate + if (stream.Type != MediaStreamType.Subtitle) + { + var bitrate = 0; + + if (!string.IsNullOrEmpty(streamInfo.bit_rate)) + { + bitrate = int.Parse(streamInfo.bit_rate, UsCulture); + } + else if (formatInfo != null && !string.IsNullOrEmpty(formatInfo.bit_rate)) + { + // If the stream info doesn't have a bitrate get the value from the media format info + bitrate = int.Parse(formatInfo.bit_rate, UsCulture); + } + + if (bitrate > 0) + { + stream.BitRate = bitrate; + } + } + + if (streamInfo.disposition != null) + { + var isDefault = GetDictionaryValue(streamInfo.disposition, "default"); + var isForced = GetDictionaryValue(streamInfo.disposition, "forced"); + + stream.IsDefault = string.Equals(isDefault, "1", StringComparison.OrdinalIgnoreCase); + + stream.IsForced = string.Equals(isForced, "1", StringComparison.OrdinalIgnoreCase); + } + + return stream; + } + + /// + /// Gets a string from an FFProbeResult tags dictionary + /// + /// The tags. + /// The key. + /// System.String. + private static string GetDictionaryValue(Dictionary tags, string key) + { + if (tags == null) + { + return null; + } + + string val; + + tags.TryGetValue(key, out val); + return val; + } + + private static string ParseChannelLayout(string input) + { + if (string.IsNullOrEmpty(input)) + { + return input; + } + + return input.Split('(').FirstOrDefault(); + } + + private static string GetAspectRatio(MediaStreamInfo info) + { + var original = info.display_aspect_ratio; + + int height; + int width; + + var parts = (original ?? string.Empty).Split(':'); + if (!(parts.Length == 2 && + int.TryParse(parts[0], NumberStyles.Any, UsCulture, out width) && + int.TryParse(parts[1], NumberStyles.Any, UsCulture, out height) && + width > 0 && + height > 0)) + { + width = info.width; + height = info.height; + } + + if (width > 0 && height > 0) + { + double ratio = width; + ratio /= height; + + if (IsClose(ratio, 1.777777778, .03)) + { + return "16:9"; + } + + if (IsClose(ratio, 1.3333333333, .05)) + { + return "4:3"; + } + + if (IsClose(ratio, 1.41)) + { + return "1.41:1"; + } + + if (IsClose(ratio, 1.5)) + { + return "1.5:1"; + } + + if (IsClose(ratio, 1.6)) + { + return "1.6:1"; + } + + if (IsClose(ratio, 1.66666666667)) + { + return "5:3"; + } + + if (IsClose(ratio, 1.85, .02)) + { + return "1.85:1"; + } + + if (IsClose(ratio, 2.35, .025)) + { + return "2.35:1"; + } + + if (IsClose(ratio, 2.4, .025)) + { + return "2.40:1"; + } + } + + return original; + } + + private static bool IsClose(double d1, double d2, double variance = .005) + { + return Math.Abs(d1 - d2) <= variance; + } + + /// + /// Gets a frame rate from a string value in ffprobe output + /// This could be a number or in the format of 2997/125. + /// + /// The value. + /// System.Nullable{System.Single}. + private static float? GetFrameRate(string value) + { + if (!string.IsNullOrEmpty(value)) + { + var parts = value.Split('/'); + + float result; + + if (parts.Length == 2) + { + result = float.Parse(parts[0], UsCulture) / float.Parse(parts[1], UsCulture); + } + else + { + result = float.Parse(parts[0], UsCulture); + } + + return float.IsNaN(result) ? (float?)null : result; + } + + return null; + } + } } diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 08797eecdc..892ec93456 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -155,5 +155,18 @@ namespace MediaBrowser.Controller.Session /// The session identifier. /// The user identifier. void RemoveAdditionalUser(Guid sessionId, Guid userId); + + /// + /// Authenticates the new session. + /// + /// The user. + /// The password. + /// Type of the client. + /// The application version. + /// The device identifier. + /// Name of the device. + /// The remote end point. + /// Task{SessionInfo}. + Task AuthenticateNewSession(User user, string password, string clientType, string appVersion, string deviceId, string deviceName, string remoteEndPoint); } } \ No newline at end of file diff --git a/MediaBrowser.Model/LiveTv/RecordingQuery.cs b/MediaBrowser.Model/LiveTv/RecordingQuery.cs index a08b7dc30b..8f82433cc7 100644 --- a/MediaBrowser.Model/LiveTv/RecordingQuery.cs +++ b/MediaBrowser.Model/LiveTv/RecordingQuery.cs @@ -1,4 +1,6 @@ -namespace MediaBrowser.Model.LiveTv +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.Model.LiveTv { /// /// Class RecordingQuery. @@ -80,5 +82,16 @@ public class SeriesTimerQuery { + /// + /// Gets or sets the sort by - SortName, Priority + /// + /// The sort by. + public string SortBy { get; set; } + + /// + /// Gets or sets the sort order. + /// + /// The sort order. + public SortOrder SortOrder { get; set; } } } diff --git a/MediaBrowser.Model/Users/AuthenticationResult.cs b/MediaBrowser.Model/Users/AuthenticationResult.cs index ea6b57e061..998aaa3a7f 100644 --- a/MediaBrowser.Model/Users/AuthenticationResult.cs +++ b/MediaBrowser.Model/Users/AuthenticationResult.cs @@ -1,9 +1,20 @@ using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Session; namespace MediaBrowser.Model.Users { public class AuthenticationResult { + /// + /// Gets or sets the user. + /// + /// The user. public UserDto User { get; set; } + + /// + /// Gets or sets the session information. + /// + /// The session information. + public SessionInfoDto SessionInfo { get; set; } } } diff --git a/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs index 31eae0c977..cd08f58288 100644 --- a/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs @@ -171,210 +171,6 @@ namespace MediaBrowser.Providers.MediaInfo } } - /// - /// Converts ffprobe stream info to our MediaStream class - /// - /// The stream info. - /// The format info. - /// MediaStream. - protected MediaStream GetMediaStream(MediaStreamInfo streamInfo, MediaFormatInfo formatInfo) - { - var stream = new MediaStream - { - Codec = streamInfo.codec_name, - Profile = streamInfo.profile, - Level = streamInfo.level, - Index = streamInfo.index - }; - - if (streamInfo.tags != null) - { - stream.Language = GetDictionaryValue(streamInfo.tags, "language"); - } - - if (string.Equals(streamInfo.codec_type, "audio", StringComparison.OrdinalIgnoreCase)) - { - stream.Type = MediaStreamType.Audio; - - stream.Channels = streamInfo.channels; - - if (!string.IsNullOrEmpty(streamInfo.sample_rate)) - { - stream.SampleRate = int.Parse(streamInfo.sample_rate, UsCulture); - } - - stream.ChannelLayout = ParseChannelLayout(streamInfo.channel_layout); - } - else if (string.Equals(streamInfo.codec_type, "subtitle", StringComparison.OrdinalIgnoreCase)) - { - stream.Type = MediaStreamType.Subtitle; - } - else if (string.Equals(streamInfo.codec_type, "video", StringComparison.OrdinalIgnoreCase)) - { - stream.Type = MediaStreamType.Video; - - stream.Width = streamInfo.width; - stream.Height = streamInfo.height; - stream.AspectRatio = GetAspectRatio(streamInfo); - - stream.AverageFrameRate = GetFrameRate(streamInfo.avg_frame_rate); - stream.RealFrameRate = GetFrameRate(streamInfo.r_frame_rate); - } - else - { - return null; - } - - // Get stream bitrate - if (stream.Type != MediaStreamType.Subtitle) - { - var bitrate = 0; - - if (!string.IsNullOrEmpty(streamInfo.bit_rate)) - { - bitrate = int.Parse(streamInfo.bit_rate, UsCulture); - } - else if (formatInfo != null && !string.IsNullOrEmpty(formatInfo.bit_rate)) - { - // If the stream info doesn't have a bitrate get the value from the media format info - bitrate = int.Parse(formatInfo.bit_rate, UsCulture); - } - - if (bitrate > 0) - { - stream.BitRate = bitrate; - } - } - - if (streamInfo.disposition != null) - { - var isDefault = GetDictionaryValue(streamInfo.disposition, "default"); - var isForced = GetDictionaryValue(streamInfo.disposition, "forced"); - - stream.IsDefault = string.Equals(isDefault, "1", StringComparison.OrdinalIgnoreCase); - - stream.IsForced = string.Equals(isForced, "1", StringComparison.OrdinalIgnoreCase); - } - - return stream; - } - - private string ParseChannelLayout(string input) - { - if (string.IsNullOrEmpty(input)) - { - return input; - } - - return input.Split('(').FirstOrDefault(); - } - - private string GetAspectRatio(MediaStreamInfo info) - { - var original = info.display_aspect_ratio; - - int height; - int width; - - var parts = (original ?? string.Empty).Split(':'); - if (!(parts.Length == 2 && - int.TryParse(parts[0], NumberStyles.Any, UsCulture, out width) && - int.TryParse(parts[1], NumberStyles.Any, UsCulture, out height) && - width > 0 && - height > 0)) - { - width = info.width; - height = info.height; - } - - if (width > 0 && height > 0) - { - double ratio = width; - ratio /= height; - - if (IsClose(ratio, 1.777777778, .03)) - { - return "16:9"; - } - - if (IsClose(ratio, 1.3333333333, .05)) - { - return "4:3"; - } - - if (IsClose(ratio, 1.41)) - { - return "1.41:1"; - } - - if (IsClose(ratio, 1.5)) - { - return "1.5:1"; - } - - if (IsClose(ratio, 1.6)) - { - return "1.6:1"; - } - - if (IsClose(ratio, 1.66666666667)) - { - return "5:3"; - } - - if (IsClose(ratio, 1.85, .02)) - { - return "1.85:1"; - } - - if (IsClose(ratio, 2.35, .025)) - { - return "2.35:1"; - } - - if (IsClose(ratio, 2.4, .025)) - { - return "2.40:1"; - } - } - - return original; - } - - private bool IsClose(double d1, double d2, double variance = .005) - { - return Math.Abs(d1 - d2) <= variance; - } - - /// - /// Gets a frame rate from a string value in ffprobe output - /// This could be a number or in the format of 2997/125. - /// - /// The value. - /// System.Nullable{System.Single}. - private float? GetFrameRate(string value) - { - if (!string.IsNullOrEmpty(value)) - { - var parts = value.Split('/'); - - float result; - - if (parts.Length == 2) - { - result = float.Parse(parts[0], UsCulture) / float.Parse(parts[1], UsCulture); - } - else - { - result = float.Parse(parts[0], UsCulture); - } - - return float.IsNaN(result) ? (float?)null : result; - } - - return null; - } - /// /// Gets a string from an FFProbeResult tags dictionary /// diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfoProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfoProvider.cs index b8cce985da..a8432969be 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfoProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfoProvider.cs @@ -3,6 +3,7 @@ using MediaBrowser.Common.MediaInfo; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.MediaInfo; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; @@ -59,32 +60,31 @@ namespace MediaBrowser.Providers.MediaInfo /// Task. protected Task Fetch(Audio audio, CancellationToken cancellationToken, MediaInfoResult data) { - var internalStreams = data.streams ?? new MediaStreamInfo[] { }; - - var mediaStreams = internalStreams.Select(s => GetMediaStream(s, data.format)) - .Where(i => i != null) - .ToList(); + var mediaStreams = MediaEncoderHelpers.GetMediaStreams(data).ToList(); audio.HasEmbeddedImage = mediaStreams.Any(i => i.Type == MediaStreamType.Video); - // Get the first audio stream - var stream = internalStreams.FirstOrDefault(s => string.Equals(s.codec_type, "audio", StringComparison.OrdinalIgnoreCase)); - - if (stream != null) + if (data.streams != null) { - // Get duration from stream properties - var duration = stream.duration; + // Get the first audio stream + var stream = data.streams.FirstOrDefault(s => string.Equals(s.codec_type, "audio", StringComparison.OrdinalIgnoreCase)); - // If it's not there go into format properties - if (string.IsNullOrEmpty(duration)) + if (stream != null) { - duration = data.format.duration; - } + // Get duration from stream properties + var duration = stream.duration; - // If we got something, parse it - if (!string.IsNullOrEmpty(duration)) - { - audio.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(duration, UsCulture)).Ticks; + // If it's not there go into format properties + if (string.IsNullOrEmpty(duration)) + { + duration = data.format.duration; + } + + // If we got something, parse it + if (!string.IsNullOrEmpty(duration)) + { + audio.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(duration, UsCulture)).Ticks; + } } } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfoProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfoProvider.cs index e386c78a4a..4ce8cf0691 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfoProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfoProvider.cs @@ -323,18 +323,7 @@ namespace MediaBrowser.Providers.MediaInfo } } - List mediaStreams; - - if (data.streams != null) - { - mediaStreams = data.streams.Select(s => GetMediaStream(s, data.format)) - .Where(i => i != null) - .ToList(); - } - else - { - mediaStreams = new List(); - } + var mediaStreams = MediaEncoderHelpers.GetMediaStreams(data).ToList(); var chapters = data.Chapters ?? new List(); diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index 182ce1cbc7..3bc146bd48 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -502,8 +502,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv _channelIdList = list.Select(i => i.Id).ToList(); progress.Report(15); - numComplete = 0; - var programs = new List(); + numComplete = 0; + var programs = new List(); foreach (var item in list) { @@ -776,6 +776,19 @@ namespace MediaBrowser.Server.Implementations.LiveTv var timers = await service.GetSeriesTimersAsync(cancellationToken).ConfigureAwait(false); + if (string.Equals(query.SortBy, "Priority", StringComparison.OrdinalIgnoreCase)) + { + timers = query.SortOrder == SortOrder.Descending ? + timers.OrderBy(i => i.Priority).ThenByDescending(i => i.Name) : + timers.OrderByDescending(i => i.Priority).ThenBy(i => i.Name); + } + else + { + timers = query.SortOrder == SortOrder.Descending ? + timers.OrderByDescending(i => i.Name) : + timers.OrderBy(i => i.Name); + } + var returnArray = timers .Select(i => { @@ -791,7 +804,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv return _tvDtoService.GetSeriesTimerInfoDto(i, service, channelName); }) - .OrderByDescending(i => i.StartDate) .ToArray(); return new QueryResult diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index 985378f8e7..d24de75cb7 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -800,5 +800,29 @@ namespace MediaBrowser.Server.Implementations.Session session.AdditionalUsers.Remove(user); } } + + /// + /// Authenticates the new session. + /// + /// The user. + /// The password. + /// Type of the client. + /// The application version. + /// The device identifier. + /// Name of the device. + /// The remote end point. + /// Task{SessionInfo}. + /// + public async Task AuthenticateNewSession(User user, string password, string clientType, string appVersion, string deviceId, string deviceName, string remoteEndPoint) + { + var result = await _userManager.AuthenticateUser(user, password).ConfigureAwait(false); + + if (!result) + { + throw new UnauthorizedAccessException("Invalid user or password entered."); + } + + return await LogSessionActivity(clientType, appVersion, deviceId, deviceName, remoteEndPoint, user).ConfigureAwait(false); + } } } \ No newline at end of file