Переглянути джерело

update hls to support mpeg2video

Luke Pulverenti 8 роки тому
батько
коміт
ce1ed2bea7

+ 2 - 2
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,

+ 1 - 1
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,

+ 11 - 1
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<List<TunerHostInfo>> DiscoverTuners(CancellationToken cancellationToken)
+        public async Task<List<TunerHostInfo>> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken)
         {
             var list = new List<TunerHostInfo>();
 
+            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);
             }
 

+ 2 - 2
Emby.Server.Implementations/LiveTv/LiveTvManager.cs

@@ -160,9 +160,9 @@ namespace Emby.Server.Implementations.LiveTv
             }).ToList();
         }
 
-        public Task<List<TunerHostInfo>> DiscoverTuners(CancellationToken cancellationToken)
+        public Task<List<TunerHostInfo>> 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)

+ 2 - 2
MediaBrowser.Api/LiveTv/LiveTvService.cs

@@ -684,7 +684,7 @@ namespace MediaBrowser.Api.LiveTv
     [Authenticated]
     public class DiscoverTuners : IReturn<List<TunerHostInfo>>
     {
-
+        public bool NewDevicesOnly { get; set; }
     }
 
     public class LiveTvService : BaseApiService
@@ -739,7 +739,7 @@ namespace MediaBrowser.Api.LiveTv
 
         public async Task<object> 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);
         }
 

+ 14 - 0
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);
+                    }
+                }
             }
         }
 

+ 21 - 7
MediaBrowser.Api/Playback/Hls/BaseHlsService.cs

@@ -41,9 +41,16 @@ namespace MediaBrowser.Api.Playback.Hls
         /// <summary>
         /// Gets the segment file extension.
         /// </summary>
-        /// <param name="state">The state.</param>
-        /// <returns>System.String.</returns>
-        protected abstract string GetSegmentFileExtension(StreamState state);
+        protected string GetSegmentFileExtension(StreamRequest request)
+        {
+            var segmentContainer = request.SegmentContainer;
+            if (!string.IsNullOrWhiteSpace(segmentContainer))
+            {
+                return "." + segmentContainer;
+            }
+
+            return ".ts";
+        }
 
         /// <summary>
         /// 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),

+ 16 - 25
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<object> 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();
         }
-
-        /// <summary>
-        /// Gets the segment file extension.
-        /// </summary>
-        /// <param name="state">The state.</param>
-        /// <returns>System.String.</returns>
-        protected override string GetSegmentFileExtension(StreamState state)
-        {
-            return GetSegmentFileExtension(state.IsOutputVideo);
-        }
-
-        protected string GetSegmentFileExtension(bool isOutputVideo)
-        {
-            return isOutputVideo ? ".ts" : ".ts";
-        }
     }
 }

+ 1 - 1
MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs

@@ -63,7 +63,7 @@ namespace MediaBrowser.Api.Playback.Hls
     /// <summary>
     /// Class GetHlsVideoSegment
     /// </summary>
-    [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; }

+ 1 - 11
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;
         }
 
-        /// <summary>
-        /// Gets the segment file extension.
-        /// </summary>
-        /// <param name="state">The state.</param>
-        /// <returns>System.String.</returns>
-        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)
         {
         }

+ 1 - 0
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

+ 1 - 1
MediaBrowser.Controller/LiveTv/ILiveTvManager.cs

@@ -382,7 +382,7 @@ namespace MediaBrowser.Controller.LiveTv
         List<IListingsProvider> ListingProviders { get; }
 
         List<NameIdPair> GetTunerHostTypes();
-        Task<List<TunerHostInfo>> DiscoverTuners(CancellationToken cancellationToken);
+        Task<List<TunerHostInfo>> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken);
 
         event EventHandler<GenericEventArgs<TimerEventInfo>> SeriesTimerCancelled;
         event EventHandler<GenericEventArgs<TimerEventInfo>> TimerCancelled;

+ 8 - 2
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

+ 3 - 1
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; }

+ 7 - 12
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");
         }
     }
-}
+}

+ 2 - 1
MediaBrowser.Model/Dlna/ProfileConditionValue.cs

@@ -21,6 +21,7 @@
         NumVideoStreams = 17,
         IsSecondaryAudio = 18,
         VideoCodecTag = 19,
-        IsAvc = 20
+        IsAvc = 20,
+        IsInterlaced = 21
     }
 }

+ 33 - 1
MediaBrowser.Model/Dlna/StreamBuilder.cs

@@ -231,6 +231,7 @@ namespace MediaBrowser.Model.Dlna
                 {
                     playlistItem.AudioCodecs = transcodingProfile.AudioCodec.Split(',');
                 }
+
                 playlistItem.SubProtocol = transcodingProfile.Protocol;
 
                 List<CodecProfile> audioCodecProfiles = new List<CodecProfile>();
@@ -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:

+ 47 - 5
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];
+            }
+        }
+        
         /// <summary>
-        /// Predicts the audio channels that will be in the output stream
-        /// </summary>
+             /// Predicts the audio channels that will be in the output stream
+             /// </summary>
         public long? TargetSize
         {
             get

+ 89 - 31
MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs

@@ -45,14 +45,21 @@ namespace MediaBrowser.Providers.Music
         public async Task<IEnumerable<RemoteSearchResult>> 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<string> 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;
+        }
+
         /// <summary>
         /// Gets the release group id internal.
         /// </summary>
         /// <param name="releaseEntryId">The release entry id.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task{System.String}.</returns>
-        private async Task<string> GetReleaseGroupId(string releaseEntryId, CancellationToken cancellationToken)
+        private async Task<string> 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<MbzUrl> _mbzUrls = null;
         private MbzUrl _chosenUrl;
 
-        private async Task<MbzUrl> GetMbzUrl()
+        private async Task<MbzUrl> 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,31 +576,44 @@ namespace MediaBrowser.Providers.Music
             return _chosenUrl;
         }
 
-        private async Task<List<MbzUrl>> RefreshMzbUrls()
+        private async Task<List<MbzUrl>> RefreshMzbUrls(bool forceMusicBrainzProper = false)
         {
             List<MbzUrl> list;
 
-            try
+            if (forceMusicBrainzProper)
             {
-                var options = new HttpRequestOptions
+                list = new List<MbzUrl>
                 {
-                    Url = "https://mb3admin.com/admin/service/standards/musicBrainzUrls",
-                    UserAgent = _appHost.Name + "/" + _appHost.ApplicationVersion
+                    new MbzUrl
+                    {
+                        url = MusicBrainzBaseUrl,
+                        throttleMs = 1000
+                    }
                 };
-
-                using (var stream = await _httpClient.Get(options).ConfigureAwait(false))
+            }
+            else
+            {
+                try
                 {
-                    var results = _json.DeserializeFromStream<List<MbzUrl>>(stream);
+                    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<List<MbzUrl>>(stream);
 
-                    list = results;
+                        list = results;
+                    }
+                    _lastMbzUrlQueryTicks = DateTime.UtcNow.Ticks;
                 }
-                _lastMbzUrlQueryTicks = DateTime.UtcNow.Ticks;
-            }
-            catch (Exception ex)
-            {
-                _logger.ErrorException("Error getting music brainz info", ex);
+                catch (Exception ex)
+                {
+                    _logger.ErrorException("Error getting music brainz info", ex);
 
-                list = new List<MbzUrl>
+                    list = new List<MbzUrl>
                 {
                     new MbzUrl
                     {
@@ -565,6 +621,7 @@ namespace MediaBrowser.Providers.Music
                         throttleMs = 1000
                     }
                 };
+                }
             }
 
             _mbzUrls = list.ToList();
@@ -572,16 +629,17 @@ namespace MediaBrowser.Providers.Music
             return list;
         }
 
+        internal Task<Stream> GetMusicBrainzResponse(string url, bool isSearch, CancellationToken cancellationToken)
+        {
+            return GetMusicBrainzResponse(url, isSearch, false, cancellationToken);
+        }
+
         /// <summary>
         /// Gets the music brainz response.
         /// </summary>
-        /// <param name="url">The URL.</param>
-        /// <param name="isSearch">if set to <c>true</c> [is search].</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task{XmlDocument}.</returns>
-        internal async Task<Stream> GetMusicBrainzResponse(string url, bool isSearch, CancellationToken cancellationToken)
+        internal async Task<Stream> 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)

+ 1 - 1
SharedVersion.cs

@@ -1,3 +1,3 @@
 using System.Reflection;
 
-[assembly: AssemblyVersion("3.2.7.5")]
+[assembly: AssemblyVersion("3.2.8.1")]