diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs index d048dcde13..c7eae91dd4 100644 --- a/Emby.Dlna/Didl/DidlBuilder.cs +++ b/Emby.Dlna/Didl/DidlBuilder.cs @@ -208,7 +208,7 @@ namespace Emby.Dlna.Didl var targetHeight = streamInfo.TargetHeight; var contentFeatureList = new ContentFeatureBuilder(_profile).BuildVideoHeader(streamInfo.Container, - streamInfo.VideoCodec, + streamInfo.TargetVideoCodec, streamInfo.TargetAudioCodec, targetWidth, targetHeight, @@ -352,7 +352,7 @@ namespace Emby.Dlna.Didl var mediaProfile = _profile.GetVideoMediaProfile(streamInfo.Container, streamInfo.TargetAudioCodec, - streamInfo.VideoCodec, + streamInfo.TargetVideoCodec, streamInfo.TargetAudioBitrate, targetWidth, targetHeight, diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index 3c07e95dbb..b73332c4bd 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -542,7 +542,7 @@ namespace Emby.Dlna.PlayTo { var list = new ContentFeatureBuilder(profile) .BuildVideoHeader(streamInfo.Container, - streamInfo.VideoCodec, + streamInfo.TargetVideoCodec, streamInfo.TargetAudioCodec, streamInfo.TargetWidth, streamInfo.TargetHeight, diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index d18004f963..eea5625244 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -2545,14 +2545,24 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV private const int TunerDiscoveryDurationMs = 3000; - public async Task> DiscoverTuners(CancellationToken cancellationToken) + public async Task> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken) { var list = new List(); + var configuredDeviceIds = GetConfiguration().TunerHosts + .Where(i => !string.IsNullOrWhiteSpace(i.DeviceId)) + .Select(i => i.DeviceId) + .ToList(); + foreach (var host in _liveTvManager.TunerHosts) { var discoveredDevices = await DiscoverDevices(host, TunerDiscoveryDurationMs, cancellationToken).ConfigureAwait(false); + if (newDevicesOnly) + { + discoveredDevices = discoveredDevices.Where(d => !configuredDeviceIds.Contains(d.DeviceId, StringComparer.OrdinalIgnoreCase)) + .ToList(); + } list.AddRange(discoveredDevices); } diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index 92ef24deab..8406d44a7e 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -160,9 +160,9 @@ namespace Emby.Server.Implementations.LiveTv }).ToList(); } - public Task> DiscoverTuners(CancellationToken cancellationToken) + public Task> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken) { - return EmbyTV.EmbyTV.Current.DiscoverTuners(cancellationToken); + return EmbyTV.EmbyTV.Current.DiscoverTuners(newDevicesOnly, cancellationToken); } void service_DataSourceChanged(object sender, EventArgs e) diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index 2f7d049361..2d8744f70b 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -684,7 +684,7 @@ namespace MediaBrowser.Api.LiveTv [Authenticated] public class DiscoverTuners : IReturn> { - + public bool NewDevicesOnly { get; set; } } public class LiveTvService : BaseApiService @@ -739,7 +739,7 @@ namespace MediaBrowser.Api.LiveTv public async Task Get(DiscoverTuners request) { - var result = await _liveTvManager.DiscoverTuners(CancellationToken.None).ConfigureAwait(false); + var result = await _liveTvManager.DiscoverTuners(request.NewDevicesOnly, CancellationToken.None).ConfigureAwait(false); return ToOptimizedResult(result); } diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index e1559cabf9..b3a00cc92c 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -697,6 +697,20 @@ namespace MediaBrowser.Api.Playback { request.SubtitleCodec = val; } + else if (i == 31) + { + if (videoRequest != null) + { + videoRequest.RequireNonAnamorphic = string.Equals("true", val, StringComparison.OrdinalIgnoreCase); + } + } + else if (i == 32) + { + if (videoRequest != null) + { + videoRequest.DeInterlace = string.Equals("true", val, StringComparison.OrdinalIgnoreCase); + } + } } } diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index aaca1793ce..98115b8406 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -41,9 +41,16 @@ namespace MediaBrowser.Api.Playback.Hls /// /// Gets the segment file extension. /// - /// The state. - /// System.String. - protected abstract string GetSegmentFileExtension(StreamState state); + protected string GetSegmentFileExtension(StreamRequest request) + { + var segmentContainer = request.SegmentContainer; + if (!string.IsNullOrWhiteSpace(segmentContainer)) + { + return "." + segmentContainer; + } + + return ".ts"; + } /// /// Gets the type of the transcoding job. @@ -261,11 +268,17 @@ namespace MediaBrowser.Api.Playback.Hls var useGenericSegmenter = false; if (useGenericSegmenter) { - var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state); + var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state.Request); var timeDeltaParam = String.Empty; - return string.Format("{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -f segment -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero -segment_time {6} {10} -individual_header_trailer 0 -segment_format mpegts -segment_list_type m3u8 -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"", + var segmentFormat = GetSegmentFileExtension(state.Request).TrimStart('.'); + if (string.Equals(segmentFormat, "ts", StringComparison.OrdinalIgnoreCase)) + { + segmentFormat = "mpegts"; + } + + return string.Format("{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -f segment -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero -segment_time {6} {10} -individual_header_trailer 0 -segment_format {11} -segment_list_type m3u8 -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"", inputModifier, EncodingHelper.GetInputArgument(state, encodingOptions), threads, @@ -276,7 +289,8 @@ namespace MediaBrowser.Api.Playback.Hls startNumberParam, outputPath, outputTsArg, - timeDeltaParam + timeDeltaParam, + segmentFormat ).Trim(); } @@ -286,7 +300,7 @@ namespace MediaBrowser.Api.Playback.Hls var args = string.Format("{0} {1} {2} -map_metadata -1 -map_chapters -1 -threads {3} {4} {5} -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero {6} -hls_time {7} -individual_header_trailer 0 -start_number {8} -hls_list_size {9}{10} -y \"{11}\"", itsOffset, inputModifier, - EncodingHelper.GetInputArgument(state, encodingOptions), + EncodingHelper.GetInputArgument(state, encodingOptions), threads, EncodingHelper.GetMapArgs(state), GetVideoArguments(state), diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index 1074a8bc18..f77a66f8e0 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -65,7 +65,7 @@ namespace MediaBrowser.Api.Playback.Hls { } - [Route("/Videos/{Id}/hls1/{PlaylistId}/{SegmentId}.ts", "GET")] + [Route("/Videos/{Id}/hls1/{PlaylistId}/{SegmentId}.{SegmentContainer}", "GET")] public class GetHlsVideoSegment : VideoStreamRequest { public string PlaylistId { get; set; } @@ -77,8 +77,7 @@ namespace MediaBrowser.Api.Playback.Hls public string SegmentId { get; set; } } - [Route("/Audio/{Id}/hls1/{PlaylistId}/{SegmentId}.aac", "GET")] - [Route("/Audio/{Id}/hls1/{PlaylistId}/{SegmentId}.ts", "GET")] + [Route("/Audio/{Id}/hls1/{PlaylistId}/{SegmentId}.{SegmentContainer}", "GET")] public class GetHlsAudioSegment : StreamRequest { public string PlaylistId { get; set; } @@ -158,7 +157,7 @@ namespace MediaBrowser.Api.Playback.Hls var segmentPath = GetSegmentPath(state, playlistPath, requestedIndex); - var segmentExtension = GetSegmentFileExtension(state); + var segmentExtension = GetSegmentFileExtension(state.Request); TranscodingJob job = null; @@ -420,7 +419,7 @@ namespace MediaBrowser.Api.Playback.Hls var filename = Path.GetFileNameWithoutExtension(playlist); - return Path.Combine(folder, filename + index.ToString(UsCulture) + GetSegmentFileExtension(state)); + return Path.Combine(folder, filename + index.ToString(UsCulture) + GetSegmentFileExtension(state.Request)); } private async Task GetSegmentResult(StreamState state, string playlistPath, @@ -740,7 +739,7 @@ namespace MediaBrowser.Api.Playback.Hls name, index.ToString(UsCulture), - GetSegmentFileExtension(isOutputVideo), + GetSegmentFileExtension(request), queryString)); index++; @@ -848,7 +847,7 @@ namespace MediaBrowser.Api.Playback.Hls var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions(); - args += " " + EncodingHelper.GetVideoQualityParam(state, EncodingHelper.GetH264Encoder(state, encodingOptions), encodingOptions, GetDefaultH264Preset()) + keyFrameArg; + args += " " + EncodingHelper.GetVideoQualityParam(state, codec, encodingOptions, GetDefaultH264Preset()) + keyFrameArg; //args += " -mixed-refs 0 -refs 3 -x264opts b_pyramid=0:weightb=0:weightp=0"; @@ -897,7 +896,7 @@ namespace MediaBrowser.Api.Playback.Hls if (useGenericSegmenter) { - var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state); + var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state.Request); var timeDeltaParam = String.Empty; @@ -907,7 +906,13 @@ namespace MediaBrowser.Api.Playback.Hls timeDeltaParam = string.Format("-segment_time_delta -{0}", startTime); } - return string.Format("{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -f segment -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero -segment_time {6} {10} -individual_header_trailer 0 -segment_format mpegts -segment_list_type m3u8 -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"", + var segmentFormat = GetSegmentFileExtension(state.Request).TrimStart('.'); + if (string.Equals(segmentFormat, "ts", StringComparison.OrdinalIgnoreCase)) + { + segmentFormat = "mpegts"; + } + + return string.Format("{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -f segment -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero -segment_time {6} {10} -individual_header_trailer 0 -segment_format {11} -segment_list_type m3u8 -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"", inputModifier, EncodingHelper.GetInputArgument(state, encodingOptions), threads, @@ -918,7 +923,8 @@ namespace MediaBrowser.Api.Playback.Hls startNumberParam, outputPath, outputTsArg, - timeDeltaParam + timeDeltaParam, + segmentFormat ).Trim(); } @@ -935,20 +941,5 @@ namespace MediaBrowser.Api.Playback.Hls outputPath ).Trim(); } - - /// - /// Gets the segment file extension. - /// - /// The state. - /// System.String. - protected override string GetSegmentFileExtension(StreamState state) - { - return GetSegmentFileExtension(state.IsOutputVideo); - } - - protected string GetSegmentFileExtension(bool isOutputVideo) - { - return isOutputVideo ? ".ts" : ".ts"; - } } } \ No newline at end of file diff --git a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs index f3683c6cba..bf78e16b3a 100644 --- a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs +++ b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs @@ -63,7 +63,7 @@ namespace MediaBrowser.Api.Playback.Hls /// /// Class GetHlsVideoSegment /// - [Route("/Videos/{Id}/hls/{PlaylistId}/{SegmentId}.ts", "GET")] + [Route("/Videos/{Id}/hls/{PlaylistId}/{SegmentId}.{SegmentContainer}", "GET")] public class GetHlsVideoSegmentLegacy : VideoStreamRequest { public string PlaylistId { get; set; } diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs index c9c6acc1b6..f9164c36f6 100644 --- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs @@ -99,7 +99,7 @@ namespace MediaBrowser.Api.Playback.Hls var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.VideoRequest.SubtitleMethod == SubtitleDeliveryMethod.Encode; var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions(); - args += " " + EncodingHelper.GetVideoQualityParam(state, EncodingHelper.GetH264Encoder(state, encodingOptions), encodingOptions, GetDefaultH264Preset()) + keyFrameArg; + args += " " + EncodingHelper.GetVideoQualityParam(state, codec, encodingOptions, GetDefaultH264Preset()) + keyFrameArg; // Add resolution params, if specified if (!hasGraphicalSubs) @@ -118,16 +118,6 @@ namespace MediaBrowser.Api.Playback.Hls return args; } - /// - /// Gets the segment file extension. - /// - /// The state. - /// System.String. - protected override string GetSegmentFileExtension(StreamState state) - { - return ".ts"; - } - public VideoHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IJsonSerializer jsonSerializer, IAuthorizationContext authorizationContext) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, jsonSerializer, authorizationContext) { } diff --git a/MediaBrowser.Api/Playback/StreamRequest.cs b/MediaBrowser.Api/Playback/StreamRequest.cs index f223c99efa..d1bf9c120c 100644 --- a/MediaBrowser.Api/Playback/StreamRequest.cs +++ b/MediaBrowser.Api/Playback/StreamRequest.cs @@ -41,6 +41,7 @@ namespace MediaBrowser.Api.Playback public string PlaySessionId { get; set; } public string LiveStreamId { get; set; } public string Tag { get; set; } + public string SegmentContainer { get; set; } } public class VideoStreamRequest : StreamRequest diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs index 0dda303a3e..5242c5b1f2 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs @@ -382,7 +382,7 @@ namespace MediaBrowser.Controller.LiveTv List ListingProviders { get; } List GetTunerHostTypes(); - Task> DiscoverTuners(CancellationToken cancellationToken); + Task> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken); event EventHandler> SeriesTimerCancelled; event EventHandler> TimerCancelled; diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 6482b68290..db23712ba0 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -733,12 +733,18 @@ namespace MediaBrowser.Controller.MediaEncoding if (videoStream.IsInterlaced) { - return false; + if (request.DeInterlace) + { + return false; + } } if (videoStream.IsAnamorphic ?? false) { - return false; + if (request.RequireNonAnamorphic) + { + return false; + } } // Can't stream copy if we're burning in subtitles diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs index 4bb180d4b2..6baf87a040 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs @@ -46,7 +46,7 @@ namespace MediaBrowser.Controller.MediaEncoding AudioBitRate = info.AudioBitrate; AudioSampleRate = info.TargetAudioSampleRate; DeviceProfile = deviceProfile; - VideoCodec = info.VideoCodec; + VideoCodec = info.TargetVideoCodec; VideoBitRate = info.VideoBitrate; AudioStreamIndex = info.AudioStreamIndex; MaxRefFrames = info.MaxRefFrames; @@ -185,6 +185,8 @@ namespace MediaBrowser.Controller.MediaEncoding [ApiMember(Name = "MaxVideoBitDepth", Description = "Optional.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] public int? MaxVideoBitDepth { get; set; } public bool RequireAvc { get; set; } + public bool DeInterlace { get; set; } + public bool RequireNonAnamorphic { get; set; } public int? TranscodingMaxAudioChannels { get; set; } public int? CpuCoreLimit { get; set; } public string OutputContainer { get; set; } diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index 4a1bf53057..77b9762064 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -734,16 +734,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles } } - var charsetFromLanguage = string.IsNullOrWhiteSpace(language) - ? null - : GetSubtitleFileCharacterSetFromLanguage(language); - - // This assumption should only be made for external subtitles - if (!string.IsNullOrWhiteSpace(charsetFromLanguage) && !string.Equals(charsetFromLanguage, "windows-1252", StringComparison.OrdinalIgnoreCase)) - { - return charsetFromLanguage; - } - var charset = await DetectCharset(path, language, protocol, cancellationToken).ConfigureAwait(false); if (!string.IsNullOrWhiteSpace(charset)) @@ -756,7 +746,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles return charset; } - return charsetFromLanguage; + if (!string.IsNullOrWhiteSpace(language)) + { + return GetSubtitleFileCharacterSetFromLanguage(language); + } + + return null; } public string GetSubtitleFileCharacterSetFromLanguage(string language) @@ -854,4 +849,4 @@ namespace MediaBrowser.MediaEncoding.Subtitles throw new ArgumentOutOfRangeException("protocol"); } } -} +} \ No newline at end of file diff --git a/MediaBrowser.Model/Dlna/ProfileConditionValue.cs b/MediaBrowser.Model/Dlna/ProfileConditionValue.cs index 7e2002f179..dbd574f864 100644 --- a/MediaBrowser.Model/Dlna/ProfileConditionValue.cs +++ b/MediaBrowser.Model/Dlna/ProfileConditionValue.cs @@ -21,6 +21,7 @@ NumVideoStreams = 17, IsSecondaryAudio = 18, VideoCodecTag = 19, - IsAvc = 20 + IsAvc = 20, + IsInterlaced = 21 } } \ No newline at end of file diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 480eb23a50..80d3ea3fb4 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -231,6 +231,7 @@ namespace MediaBrowser.Model.Dlna { playlistItem.AudioCodecs = transcodingProfile.AudioCodec.Split(','); } + playlistItem.SubProtocol = transcodingProfile.Protocol; List audioCodecProfiles = new List(); @@ -479,7 +480,7 @@ namespace MediaBrowser.Model.Dlna playlistItem.AudioCodecs = transcodingProfile.AudioCodec.Split(','); - playlistItem.VideoCodec = transcodingProfile.VideoCodec; + playlistItem.VideoCodecs = transcodingProfile.VideoCodec.Split(','); playlistItem.CopyTimestamps = transcodingProfile.CopyTimestamps; playlistItem.EnableSubtitlesInManifest = transcodingProfile.EnableSubtitlesInManifest; @@ -1137,6 +1138,37 @@ namespace MediaBrowser.Model.Dlna break; } case ProfileConditionValue.IsAnamorphic: + { + bool isAnamorphic; + if (bool.TryParse(value, out isAnamorphic)) + { + if (isAnamorphic && condition.Condition == ProfileConditionType.Equals) + { + item.RequireNonAnamorphic = true; + } + else if (!isAnamorphic && condition.Condition == ProfileConditionType.NotEquals) + { + item.RequireNonAnamorphic = true; + } + } + break; + } + case ProfileConditionValue.IsInterlaced: + { + bool isInterlaced; + if (bool.TryParse(value, out isInterlaced)) + { + if (isInterlaced && condition.Condition == ProfileConditionType.Equals) + { + item.DeInterlace = true; + } + else if (!isInterlaced && condition.Condition == ProfileConditionType.NotEquals) + { + item.DeInterlace = true; + } + } + break; + } case ProfileConditionValue.AudioProfile: case ProfileConditionValue.Has64BitOffsets: case ProfileConditionValue.PacketLength: diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index a85e6085b2..7e42e7faee 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -18,6 +18,7 @@ namespace MediaBrowser.Model.Dlna public StreamInfo() { AudioCodecs = new string[] { }; + VideoCodecs = new string[] { }; SubtitleCodecs = new string[] { }; } @@ -34,13 +35,15 @@ namespace MediaBrowser.Model.Dlna public long StartPositionTicks { get; set; } - public string VideoCodec { get; set; } public string VideoProfile { get; set; } public bool RequireAvc { get; set; } + public bool DeInterlace { get; set; } + public bool RequireNonAnamorphic { get; set; } public bool CopyTimestamps { get; set; } public bool EnableSubtitlesInManifest { get; set; } public string[] AudioCodecs { get; set; } + public string[] VideoCodecs { get; set; } public int? AudioStreamIndex { get; set; } @@ -204,11 +207,15 @@ namespace MediaBrowser.Model.Dlna string.Empty : string.Join(",", item.AudioCodecs); + string videoCodecs = item.VideoCodecs.Length == 0 ? + string.Empty : + string.Join(",", item.VideoCodecs); + 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("VideoCodec", videoCodecs)); list.Add(new NameValuePair("AudioCodec", audioCodecs)); 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)); @@ -232,7 +239,9 @@ namespace MediaBrowser.Model.Dlna // } //} - if (StringHelper.EqualsIgnoreCase(item.SubProtocol, "hls") && !forceStartPosition) + var isHls = StringHelper.EqualsIgnoreCase(item.SubProtocol, "hls"); + + if (isHls && !forceStartPosition) { list.Add(new NameValuePair("StartTimeTicks", string.Empty)); } @@ -276,6 +285,14 @@ namespace MediaBrowser.Model.Dlna list.Add(new NameValuePair("SubtitleCodec", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Embed ? subtitleCodecs : string.Empty)); + list.Add(new NameValuePair("RequireNonAnamorphic", item.RequireNonAnamorphic.ToString().ToLower())); + list.Add(new NameValuePair("DeInterlace", item.DeInterlace.ToString().ToLower())); + + if (!isDlna && isHls) + { + list.Add(new NameValuePair("SegmentContainer", item.Container ?? string.Empty)); + } + return list; } @@ -609,9 +626,34 @@ namespace MediaBrowser.Model.Dlna } } + public string TargetVideoCodec + { + get + { + MediaStream stream = TargetVideoStream; + + string inputCodec = stream == null ? null : stream.Codec; + + if (IsDirectStream) + { + return inputCodec; + } + + foreach (string codec in VideoCodecs) + { + if (StringHelper.EqualsIgnoreCase(codec, inputCodec)) + { + return codec; + } + } + + return VideoCodecs.Length == 0 ? null : VideoCodecs[0]; + } + } + /// - /// Predicts the audio channels that will be in the output stream - /// + /// Predicts the audio channels that will be in the output stream + /// public long? TargetSize { get diff --git a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs index e6eb0951d9..4aefb62c8e 100644 --- a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs @@ -45,14 +45,21 @@ namespace MediaBrowser.Providers.Music public async Task> GetSearchResults(AlbumInfo searchInfo, CancellationToken cancellationToken) { var releaseId = searchInfo.GetReleaseId(); + var releaseGroupId = searchInfo.GetReleaseGroupId(); string url = null; var isNameSearch = false; + bool forceMusicBrainzProper = false; if (!string.IsNullOrEmpty(releaseId)) { url = string.Format("/ws/2/release/?query=reid:{0}", releaseId); } + else if (!string.IsNullOrEmpty(releaseGroupId)) + { + url = string.Format("/ws/2/release?release-group={0}", releaseGroupId); + forceMusicBrainzProper = true; + } else { var artistMusicBrainzId = searchInfo.GetMusicBrainzArtistId(); @@ -75,7 +82,7 @@ namespace MediaBrowser.Providers.Music if (!string.IsNullOrWhiteSpace(url)) { - using (var stream = await GetMusicBrainzResponse(url, isNameSearch, cancellationToken).ConfigureAwait(false)) + using (var stream = await GetMusicBrainzResponse(url, isNameSearch, forceMusicBrainzProper, cancellationToken).ConfigureAwait(false)) { return GetResultsFromResponse(stream); } @@ -131,7 +138,14 @@ namespace MediaBrowser.Providers.Music Item = new MusicAlbum() }; - if (string.IsNullOrEmpty(releaseId)) + // If we have a release group Id but not a release Id... + if (string.IsNullOrWhiteSpace(releaseId) && !string.IsNullOrWhiteSpace(releaseGroupId)) + { + releaseId = await GetReleaseIdFromReleaseGroupId(releaseGroupId, cancellationToken).ConfigureAwait(false); + result.HasMetadata = true; + } + + if (string.IsNullOrWhiteSpace(releaseId)) { var artistMusicBrainzId = id.GetMusicBrainzArtistId(); @@ -139,13 +153,13 @@ namespace MediaBrowser.Providers.Music if (releaseResult != null) { - if (!string.IsNullOrEmpty(releaseResult.ReleaseId)) + if (!string.IsNullOrWhiteSpace(releaseResult.ReleaseId)) { releaseId = releaseResult.ReleaseId; result.HasMetadata = true; } - if (!string.IsNullOrEmpty(releaseResult.ReleaseGroupId)) + if (!string.IsNullOrWhiteSpace(releaseResult.ReleaseGroupId)) { releaseGroupId = releaseResult.ReleaseGroupId; result.HasMetadata = true; @@ -157,13 +171,13 @@ namespace MediaBrowser.Providers.Music } // If we have a release Id but not a release group Id... - if (!string.IsNullOrEmpty(releaseId) && string.IsNullOrEmpty(releaseGroupId)) + if (!string.IsNullOrWhiteSpace(releaseId) && string.IsNullOrWhiteSpace(releaseGroupId)) { - releaseGroupId = await GetReleaseGroupId(releaseId, cancellationToken).ConfigureAwait(false); + releaseGroupId = await GetReleaseGroupFromReleaseId(releaseId, cancellationToken).ConfigureAwait(false); result.HasMetadata = true; } - if (!string.IsNullOrEmpty(releaseId) || !string.IsNullOrEmpty(releaseGroupId)) + if (!string.IsNullOrWhiteSpace(releaseId) || !string.IsNullOrWhiteSpace(releaseGroupId)) { result.HasMetadata = true; } @@ -411,13 +425,42 @@ namespace MediaBrowser.Providers.Music } } + private async Task GetReleaseIdFromReleaseGroupId(string releaseGroupId, CancellationToken cancellationToken) + { + var url = string.Format("/ws/2/release?release-group={0}", releaseGroupId); + + using (var stream = await GetMusicBrainzResponse(url, true, true, cancellationToken).ConfigureAwait(false)) + { + using (var oReader = new StreamReader(stream, Encoding.UTF8)) + { + var settings = _xmlSettings.Create(false); + + settings.CheckCharacters = false; + settings.IgnoreProcessingInstructions = true; + settings.IgnoreComments = true; + + using (var reader = XmlReader.Create(oReader, settings)) + { + var result = ReleaseResult.Parse(reader).FirstOrDefault(); + + if (result != null) + { + return result.ReleaseId; + } + } + } + } + + return null; + } + /// /// Gets the release group id internal. /// /// The release entry id. /// The cancellation token. /// Task{System.String}. - private async Task GetReleaseGroupId(string releaseEntryId, CancellationToken cancellationToken) + private async Task GetReleaseGroupFromReleaseId(string releaseEntryId, CancellationToken cancellationToken) { var url = string.Format("/ws/2/release-group/?query=reid:{0}", releaseEntryId); @@ -514,11 +557,11 @@ namespace MediaBrowser.Providers.Music private List _mbzUrls = null; private MbzUrl _chosenUrl; - private async Task GetMbzUrl() + private async Task GetMbzUrl(bool forceMusicBrainzProper = false) { if (_chosenUrl == null || _mbzUrls == null || (DateTime.UtcNow.Ticks - _lastMbzUrlQueryTicks) > TimeSpan.FromHours(12).Ticks) { - var urls = await RefreshMzbUrls().ConfigureAwait(false); + var urls = await RefreshMzbUrls(forceMusicBrainzProper).ConfigureAwait(false); if (urls.Count > 1) { @@ -533,30 +576,12 @@ namespace MediaBrowser.Providers.Music return _chosenUrl; } - private async Task> RefreshMzbUrls() + private async Task> RefreshMzbUrls(bool forceMusicBrainzProper = false) { List list; - try + if (forceMusicBrainzProper) { - var options = new HttpRequestOptions - { - Url = "https://mb3admin.com/admin/service/standards/musicBrainzUrls", - UserAgent = _appHost.Name + "/" + _appHost.ApplicationVersion - }; - - using (var stream = await _httpClient.Get(options).ConfigureAwait(false)) - { - var results = _json.DeserializeFromStream>(stream); - - list = results; - } - _lastMbzUrlQueryTicks = DateTime.UtcNow.Ticks; - } - catch (Exception ex) - { - _logger.ErrorException("Error getting music brainz info", ex); - list = new List { new MbzUrl @@ -566,22 +591,55 @@ namespace MediaBrowser.Providers.Music } }; } + else + { + try + { + var options = new HttpRequestOptions + { + Url = "https://mb3admin.com/admin/service/standards/musicBrainzUrls", + UserAgent = _appHost.Name + "/" + _appHost.ApplicationVersion + }; + + using (var stream = await _httpClient.Get(options).ConfigureAwait(false)) + { + var results = _json.DeserializeFromStream>(stream); + + list = results; + } + _lastMbzUrlQueryTicks = DateTime.UtcNow.Ticks; + } + catch (Exception ex) + { + _logger.ErrorException("Error getting music brainz info", ex); + + list = new List + { + new MbzUrl + { + url = MusicBrainzBaseUrl, + throttleMs = 1000 + } + }; + } + } _mbzUrls = list.ToList(); return list; } + internal Task GetMusicBrainzResponse(string url, bool isSearch, CancellationToken cancellationToken) + { + return GetMusicBrainzResponse(url, isSearch, false, cancellationToken); + } + /// /// Gets the music brainz response. /// - /// The URL. - /// if set to true [is search]. - /// The cancellation token. - /// Task{XmlDocument}. - internal async Task GetMusicBrainzResponse(string url, bool isSearch, CancellationToken cancellationToken) + internal async Task GetMusicBrainzResponse(string url, bool isSearch, bool forceMusicBrainzProper, CancellationToken cancellationToken) { - var urlInfo = await GetMbzUrl().ConfigureAwait(false); + var urlInfo = await GetMbzUrl(forceMusicBrainzProper).ConfigureAwait(false); var throttleMs = urlInfo.throttleMs; if (throttleMs > 0) diff --git a/SharedVersion.cs b/SharedVersion.cs index b001cfb52b..1c2997eedb 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,3 +1,3 @@ using System.Reflection; -[assembly: AssemblyVersion("3.2.7.5")] +[assembly: AssemblyVersion("3.2.8.1")]