浏览代码

Merge branch 'dev' into beta

Luke Pulverenti 9 年之前
父节点
当前提交
e72f59f8cd
共有 26 个文件被更改,包括 463 次插入218 次删除
  1. 0 1
      MediaBrowser.Api/StartupWizardService.cs
  2. 4 0
      MediaBrowser.Api/UserLibrary/ItemsService.cs
  3. 1 1
      MediaBrowser.Dlna/Server/DescriptionXmlBuilder.cs
  4. 1 1
      MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs
  5. 13 11
      MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs
  6. 139 72
      MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs
  7. 104 75
      MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs
  8. 27 20
      MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs
  9. 0 2
      MediaBrowser.Model/Configuration/CinemaModeConfiguration.cs
  10. 0 2
      MediaBrowser.Model/Configuration/ServerConfiguration.cs
  11. 1 1
      MediaBrowser.Model/Dto/MediaSourceInfo.cs
  12. 6 1
      MediaBrowser.Providers/Manager/ImageSaver.cs
  13. 0 15
      MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs
  14. 139 3
      MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs
  15. 4 0
      MediaBrowser.Server.Implementations/Library/LibraryManager.cs
  16. 8 3
      MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
  17. 1 1
      MediaBrowser.Server.Implementations/Localization/Core/de.json
  18. 1 1
      MediaBrowser.Server.Implementations/Localization/Core/es-MX.json
  19. 1 1
      MediaBrowser.Server.Implementations/Localization/Core/fr.json
  20. 1 1
      MediaBrowser.Server.Implementations/Localization/Core/kk.json
  21. 1 1
      MediaBrowser.Server.Implementations/Localization/Core/nl.json
  22. 1 1
      MediaBrowser.Server.Implementations/Localization/Core/pt-BR.json
  23. 1 1
      MediaBrowser.Server.Implementations/Localization/Core/ru.json
  24. 1 0
      MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs
  25. 5 0
      MediaBrowser.WebDashboard/Api/DashboardService.cs
  26. 3 3
      MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj

+ 0 - 1
MediaBrowser.Api/StartupWizardService.cs

@@ -68,7 +68,6 @@ namespace MediaBrowser.Api
             _config.Configuration.EnableLocalizedGuids = true;
             _config.Configuration.EnableCustomPathSubFolders = true;
             _config.Configuration.EnableDateLastRefresh = true;
-            _config.Configuration.MergeMetadataAndImagesByName = true;
             _config.SaveConfiguration();
         }
 

+ 4 - 0
MediaBrowser.Api/UserLibrary/ItemsService.cs

@@ -116,6 +116,10 @@ namespace MediaBrowser.Api.UserLibrary
             {
                 item = user == null ? _libraryManager.RootFolder : user.RootFolder;
             }
+            else if (string.Equals(request.IncludeItemTypes, "BoxSet", StringComparison.OrdinalIgnoreCase))
+            {
+                item = user == null ? _libraryManager.RootFolder : user.RootFolder;
+            }
 
             // Default list type = children
 

+ 1 - 1
MediaBrowser.Dlna/Server/DescriptionXmlBuilder.cs

@@ -122,7 +122,7 @@ namespace MediaBrowser.Dlna.Server
                 builder.Append("<serialNumber>" + SecurityElement.Escape(_profile.SerialNumber) + "</serialNumber>");
             }
 
-            builder.Append("<UDN>uuid:" + SecurityElement.Escape(_serverUdn) + "</UDN>");
+            builder.Append("<UDN>uuid:" + SecurityElement.Escape(_serverId) + "</UDN>");
             builder.Append("<presentationURL>" + SecurityElement.Escape(_serverAddress) + "</presentationURL>");
 
             if (!EnableAbsoluteUrls)

+ 1 - 1
MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs

@@ -176,7 +176,7 @@ namespace MediaBrowser.LocalMetadata.Images
                 "default"
             };
 
-            if (item is MusicAlbum || item is MusicArtist)
+            if (item is MusicAlbum || item is MusicArtist || item is Photo)
             {
                 // these prefer folder
                 names.Insert(0, "poster");

+ 13 - 11
MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs

@@ -19,38 +19,40 @@ namespace MediaBrowser.MediaEncoding.Encoder
         {
         }
 
-        protected override string GetCommandLineArguments(EncodingJob job)
+        protected override string GetCommandLineArguments(EncodingJob state)
         {
             var audioTranscodeParams = new List<string>();
 
-            var bitrate = job.OutputAudioBitrate;
+            var bitrate = state.OutputAudioBitrate;
 
             if (bitrate.HasValue)
             {
                 audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(UsCulture));
             }
 
-            if (job.OutputAudioChannels.HasValue)
+            if (state.OutputAudioChannels.HasValue)
             {
-                audioTranscodeParams.Add("-ac " + job.OutputAudioChannels.Value.ToString(UsCulture));
+                audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(UsCulture));
             }
 
-            if (job.OutputAudioSampleRate.HasValue)
+            if (state.OutputAudioSampleRate.HasValue)
             {
-                audioTranscodeParams.Add("-ar " + job.OutputAudioSampleRate.Value.ToString(UsCulture));
+                audioTranscodeParams.Add("-ar " + state.OutputAudioSampleRate.Value.ToString(UsCulture));
             }
 
-            var threads = GetNumberOfThreads(job, false);
+            const string vn = " -vn";
 
-            var inputModifier = GetInputModifier(job);
+            var threads = GetNumberOfThreads(state, false);
+
+            var inputModifier = GetInputModifier(state);
 
             return string.Format("{0} {1} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1 -y \"{5}\"",
                 inputModifier,
-                GetInputArgument(job),
+                GetInputArgument(state),
                 threads,
-                " -vn",
+                vn,
                 string.Join(" ", audioTranscodeParams.ToArray()),
-                job.OutputFilePath).Trim();
+                state.OutputFilePath).Trim();
         }
 
         protected override string GetOutputFileExtension(EncodingJob state)

+ 139 - 72
MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs

@@ -303,15 +303,15 @@ namespace MediaBrowser.MediaEncoding.Encoder
             return job.Options.CpuCoreLimit ?? 0;
         }
 
-        protected string GetInputModifier(EncodingJob job, bool genPts = true)
+        protected string GetInputModifier(EncodingJob state, bool genPts = true)
         {
             var inputModifier = string.Empty;
 
-            var probeSize = GetProbeSizeArgument(job);
+            var probeSize = GetProbeSizeArgument(state);
             inputModifier += " " + probeSize;
             inputModifier = inputModifier.Trim();
 
-            var userAgentParam = GetUserAgentParam(job);
+            var userAgentParam = GetUserAgentParam(state);
 
             if (!string.IsNullOrWhiteSpace(userAgentParam))
             {
@@ -320,35 +320,43 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
             inputModifier = inputModifier.Trim();
 
-            inputModifier += " " + GetFastSeekCommandLineParameter(job.Options);
+            inputModifier += " " + GetFastSeekCommandLineParameter(state.Options);
             inputModifier = inputModifier.Trim();
 
-            if (job.IsVideoRequest && genPts)
+            if (state.IsVideoRequest && genPts)
             {
                 inputModifier += " -fflags +genpts";
             }
 
-            if (!string.IsNullOrEmpty(job.InputAudioSync))
+            if (!string.IsNullOrEmpty(state.InputAudioSync))
             {
-                inputModifier += " -async " + job.InputAudioSync;
+                inputModifier += " -async " + state.InputAudioSync;
             }
 
-            if (!string.IsNullOrEmpty(job.InputVideoSync))
+            if (!string.IsNullOrEmpty(state.InputVideoSync))
             {
-                inputModifier += " -vsync " + job.InputVideoSync;
+                inputModifier += " -vsync " + state.InputVideoSync;
             }
 
-            if (job.ReadInputAtNativeFramerate)
+            if (state.ReadInputAtNativeFramerate)
             {
                 inputModifier += " -re";
             }
 
-            var videoDecoder = GetVideoDecoder(job);
+            var videoDecoder = GetVideoDecoder(state);
             if (!string.IsNullOrWhiteSpace(videoDecoder))
             {
                 inputModifier += " " + videoDecoder;
             }
 
+            //if (state.IsVideoRequest)
+            //{
+            //    if (string.Equals(state.OutputContainer, "mkv", StringComparison.OrdinalIgnoreCase))
+            //    {
+            //        //inputModifier += " -noaccurate_seek";
+            //    }
+            //}
+
             return inputModifier;
         }
 
@@ -392,11 +400,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
             return null;
         }
 
-        private string GetUserAgentParam(EncodingJob job)
+        private string GetUserAgentParam(EncodingJob state)
         {
             string useragent = null;
 
-            job.RemoteHttpHeaders.TryGetValue("User-Agent", out useragent);
+            state.RemoteHttpHeaders.TryGetValue("User-Agent", out useragent);
 
             if (!string.IsNullOrWhiteSpace(useragent))
             {
@@ -409,31 +417,31 @@ namespace MediaBrowser.MediaEncoding.Encoder
         /// <summary>
         /// Gets the probe size argument.
         /// </summary>
-        /// <param name="job">The job.</param>
+        /// <param name="state">The state.</param>
         /// <returns>System.String.</returns>
-        private string GetProbeSizeArgument(EncodingJob job)
+        private string GetProbeSizeArgument(EncodingJob state)
         {
-            if (job.PlayableStreamFileNames.Count > 0)
+            if (state.PlayableStreamFileNames.Count > 0)
             {
-                return MediaEncoder.GetProbeSizeArgument(job.PlayableStreamFileNames.ToArray(), job.InputProtocol);
+                return MediaEncoder.GetProbeSizeArgument(state.PlayableStreamFileNames.ToArray(), state.InputProtocol);
             }
 
-            return MediaEncoder.GetProbeSizeArgument(new[] { job.MediaPath }, job.InputProtocol);
+            return MediaEncoder.GetProbeSizeArgument(new[] { state.MediaPath }, state.InputProtocol);
         }
 
         /// <summary>
         /// Gets the fast seek command line parameter.
         /// </summary>
-        /// <param name="options">The options.</param>
+        /// <param name="request">The request.</param>
         /// <returns>System.String.</returns>
         /// <value>The fast seek command line parameter.</value>
-        protected string GetFastSeekCommandLineParameter(EncodingJobOptions options)
+        protected string GetFastSeekCommandLineParameter(EncodingJobOptions request)
         {
-            var time = options.StartTimeTicks;
+            var time = request.StartTimeTicks ?? 0;
 
-            if (time.HasValue && time.Value > 0)
+            if (time > 0)
             {
-                return string.Format("-ss {0}", MediaEncoder.GetTimeParameter(time.Value));
+                return string.Format("-ss {0}", MediaEncoder.GetTimeParameter(time));
             }
 
             return string.Empty;
@@ -442,34 +450,35 @@ namespace MediaBrowser.MediaEncoding.Encoder
         /// <summary>
         /// Gets the input argument.
         /// </summary>
-        /// <param name="job">The job.</param>
+        /// <param name="state">The state.</param>
         /// <returns>System.String.</returns>
-        protected string GetInputArgument(EncodingJob job)
+        protected string GetInputArgument(EncodingJob state)
         {
-            var arg = "-i " + GetInputPathArgument(job);
+            var arg = string.Format("-i {0}", GetInputPathArgument(state));
 
-            if (job.SubtitleStream != null)
+            if (state.SubtitleStream != null)
             {
-                if (job.SubtitleStream.IsExternal && !job.SubtitleStream.IsTextSubtitleStream)
+                if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)
                 {
-                    arg += " -i \"" + job.SubtitleStream.Path + "\"";
+                    arg += " -i \"" + state.SubtitleStream.Path + "\"";
                 }
             }
 
-            return arg;
+            return arg.Trim();
         }
 
-        private string GetInputPathArgument(EncodingJob job)
+        private string GetInputPathArgument(EncodingJob state)
         {
-            var protocol = job.InputProtocol;
+            var protocol = state.InputProtocol;
+            var mediaPath = state.MediaPath ?? string.Empty;
 
-            var inputPath = new[] { job.MediaPath };
+            var inputPath = new[] { mediaPath };
 
-            if (job.IsInputVideo)
+            if (state.IsInputVideo)
             {
-                if (!(job.VideoType == VideoType.Iso && job.IsoMount == null))
+                if (!(state.VideoType == VideoType.Iso && state.IsoMount == null))
                 {
-                    inputPath = MediaEncoderHelpers.GetInputArgument(FileSystem, job.MediaPath, job.InputProtocol, job.IsoMount, job.PlayableStreamFileNames);
+                    inputPath = MediaEncoderHelpers.GetInputArgument(FileSystem, mediaPath, state.InputProtocol, state.IsoMount, state.PlayableStreamFileNames);
                 }
             }
 
@@ -491,7 +500,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
                 }, false, cancellationToken).ConfigureAwait(false);
 
-                AttachMediaStreamInfo(state, liveStreamResponse.MediaSource, state.Options);
+                AttachMediaSourceInfo(state, liveStreamResponse.MediaSource, state.Options);
 
                 if (state.IsVideoRequest)
                 {
@@ -505,11 +514,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
             }
         }
 
-        private void AttachMediaStreamInfo(EncodingJob state,
+        private void AttachMediaSourceInfo(EncodingJob state,
           MediaSourceInfo mediaSource,
           EncodingJobOptions videoRequest)
         {
-            EncodingJobFactory.AttachMediaStreamInfo(state, mediaSource, videoRequest);
+            EncodingJobFactory.AttachMediaSourceInfo(state, mediaSource, videoRequest);
         }
 
         /// <summary>
@@ -572,7 +581,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
             {
                 param = "-preset superfast";
 
-                param += " -crf 28";
+                param += " -crf 23";
             }
 
             else if (string.Equals(videoCodec, "libx265", StringComparison.OrdinalIgnoreCase))
@@ -582,6 +591,19 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 param += " -crf 28";
             }
 
+            // h264 (h264_qsv)
+            else if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
+            {
+                param = "-preset 7 -look_ahead 0";
+
+            }
+
+            // h264 (libnvenc)
+            else if (string.Equals(videoCodec, "libnvenc", StringComparison.OrdinalIgnoreCase))
+            {
+                param = "-preset high-performance";
+            }
+
             // webm
             else if (string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase))
             {
@@ -644,9 +666,53 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 param += " -profile:v " + state.Options.Profile;
             }
 
-            if (state.Options.Level.HasValue)
+            var levelString = state.Options.Level.HasValue ? state.Options.Level.Value.ToString(CultureInfo.InvariantCulture) : null;
+
+            if (!string.IsNullOrEmpty(levelString))
             {
-                param += " -level " + state.Options.Level.Value.ToString(UsCulture);
+                var h264Encoder = EncodingJobFactory.GetH264Encoder(state, GetEncodingOptions());
+
+                // h264_qsv and libnvenc expect levels to be expressed as a decimal. libx264 supports decimal and non-decimal format
+                if (String.Equals(h264Encoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) || String.Equals(h264Encoder, "libnvenc", StringComparison.OrdinalIgnoreCase))
+                {
+                    switch (levelString)
+                    {
+                        case "30":
+                            param += " -level 3";
+                            break;
+                        case "31":
+                            param += " -level 3.1";
+                            break;
+                        case "32":
+                            param += " -level 3.2";
+                            break;
+                        case "40":
+                            param += " -level 4";
+                            break;
+                        case "41":
+                            param += " -level 4.1";
+                            break;
+                        case "42":
+                            param += " -level 4.2";
+                            break;
+                        case "50":
+                            param += " -level 5";
+                            break;
+                        case "51":
+                            param += " -level 5.1";
+                            break;
+                        case "52":
+                            param += " -level 5.2";
+                            break;
+                        default:
+                            param += " -level " + levelString;
+                            break;
+                    }
+                }
+                else
+                {
+                    param += " -level " + levelString;
+                }
             }
 
             return "-pix_fmt yuv420p " + param;
@@ -658,15 +724,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
             if (bitrate.HasValue)
             {
-                var hasFixedResolution = state.Options.HasFixedResolution;
-
                 if (string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase))
                 {
-                    if (hasFixedResolution)
-                    {
-                        return string.Format(" -minrate:v ({0}*.90) -maxrate:v ({0}*1.10) -bufsize:v {0} -b:v {0}", bitrate.Value.ToString(UsCulture));
-                    }
-
                     // With vpx when crf is used, b:v becomes a max rate
                     // https://trac.ffmpeg.org/wiki/vpxEncodingGuide. But higher bitrate source files -b:v causes judder so limite the bitrate but dont allow it to "saturate" the bitrate. So dont contrain it down just up.
                     return string.Format(" -maxrate:v {0} -bufsize:v ({0}*2) -b:v {0}", bitrate.Value.ToString(UsCulture));
@@ -677,20 +736,15 @@ namespace MediaBrowser.MediaEncoding.Encoder
                     return string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture));
                 }
 
-                // H264
-                if (hasFixedResolution)
+                // h264
+                if (isHls)
                 {
-                    if (isHls)
-                    {
-                        return string.Format(" -b:v {0} -maxrate ({0}*.80) -bufsize {0}", bitrate.Value.ToString(UsCulture));
-                    }
-
-                    return string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture));
+                    return string.Format(" -b:v {0} -maxrate {0} -bufsize {1}",
+                        bitrate.Value.ToString(UsCulture),
+                        (bitrate.Value * 2).ToString(UsCulture));
                 }
 
-                return string.Format(" -maxrate {0} -bufsize {1}",
-                    bitrate.Value.ToString(UsCulture),
-                    (bitrate.Value * 2).ToString(UsCulture));
+                return string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture));
             }
 
             return string.Empty;
@@ -698,20 +752,23 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
         protected double? GetFramerateParam(EncodingJob state)
         {
-            if (state.Options.Framerate.HasValue)
+            if (state.Options != null)
             {
-                return state.Options.Framerate.Value;
-            }
-
-            var maxrate = state.Options.MaxFramerate;
+                if (state.Options.Framerate.HasValue)
+                {
+                    return state.Options.Framerate.Value;
+                }
 
-            if (maxrate.HasValue && state.VideoStream != null)
-            {
-                var contentRate = state.VideoStream.AverageFrameRate ?? state.VideoStream.RealFrameRate;
+                var maxrate = state.Options.MaxFramerate;
 
-                if (contentRate.HasValue && contentRate.Value > maxrate.Value)
+                if (maxrate.HasValue && state.VideoStream != null)
                 {
-                    return maxrate;
+                    var contentRate = state.VideoStream.AverageFrameRate ?? state.VideoStream.RealFrameRate;
+
+                    if (contentRate.HasValue && contentRate.Value > maxrate.Value)
+                    {
+                        return maxrate;
+                    }
                 }
             }
 
@@ -852,7 +909,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
             {
                 var maxWidthParam = request.MaxWidth.Value.ToString(UsCulture);
 
-                filters.Add(string.Format("scale=min(iw\\,{0}):trunc(ow/dar/2)*2", maxWidthParam));
+                filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,{0})/2)*2:trunc(ow/dar/2)*2", maxWidthParam));
             }
 
             // If a max height was requested
@@ -863,6 +920,14 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 filters.Add(string.Format("scale=trunc(oh*a/2)*2:min(ih\\,{0})", maxHeightParam));
             }
 
+            if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
+            {
+                if (filters.Count > 1)
+                {
+                    //filters[filters.Count - 1] += ":flags=fast_bilinear";
+                }
+            }
+
             var output = string.Empty;
 
             if (state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream)
@@ -917,8 +982,10 @@ namespace MediaBrowser.MediaEncoding.Encoder
                     seconds.ToString(UsCulture));
             }
 
+            var mediaPath = state.MediaPath ?? string.Empty;
+
             return string.Format("subtitles='{0}:si={1}',setpts=PTS -{2}/TB",
-                MediaEncoder.EscapeSubtitleFilterPath(state.MediaPath),
+                MediaEncoder.EscapeSubtitleFilterPath(mediaPath),
                 state.InternalSubtitleStreamOffset.ToString(UsCulture),
                 seconds.ToString(UsCulture));
         }

+ 104 - 75
MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs

@@ -10,6 +10,7 @@ using MediaBrowser.Model.MediaInfo;
 using System;
 using System.Collections.Generic;
 using System.Globalization;
+using System.IO;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
@@ -66,38 +67,56 @@ namespace MediaBrowser.MediaEncoding.Encoder
                ? mediaSources.First()
                : mediaSources.First(i => string.Equals(i.Id, request.MediaSourceId));
 
-            AttachMediaStreamInfo(state, mediaSource, options);
+            var videoRequest = state.Options;
 
-            state.OutputAudioBitrate = GetAudioBitrateParam(request, state.AudioStream);
+            AttachMediaSourceInfo(state, mediaSource, videoRequest);
+
+            //var container = Path.GetExtension(state.RequestedUrl);
+
+            //if (string.IsNullOrEmpty(container))
+            //{
+            //    container = request.Static ?
+            //        state.InputContainer :
+            //        (Path.GetExtension(GetOutputFilePath(state)) ?? string.Empty).TrimStart('.');
+            //}
+
+            //state.OutputContainer = (container ?? string.Empty).TrimStart('.');
+
+            state.OutputAudioBitrate = GetAudioBitrateParam(state.Options, state.AudioStream);
             state.OutputAudioSampleRate = request.AudioSampleRate;
 
-            state.OutputAudioCodec = GetAudioCodec(request);
+            state.OutputAudioCodec = state.Options.AudioCodec;
 
-            state.OutputAudioChannels = GetNumAudioChannelsParam(request, state.AudioStream, state.OutputAudioCodec);
+            state.OutputAudioChannels = GetNumAudioChannelsParam(state.Options, state.AudioStream, state.OutputAudioCodec);
 
-            if (isVideoRequest)
+            if (videoRequest != null)
             {
-                state.OutputVideoCodec = GetVideoCodec(request);
-                state.OutputVideoBitrate = GetVideoBitrateParamValue(request, state.VideoStream);
+                state.OutputVideoCodec = state.Options.VideoCodec;
+                state.OutputVideoBitrate = GetVideoBitrateParamValue(state.Options, state.VideoStream);
 
                 if (state.OutputVideoBitrate.HasValue)
                 {
                     var resolution = ResolutionNormalizer.Normalize(
-						state.VideoStream == null ? (int?)null : state.VideoStream.BitRate,
-						state.OutputVideoBitrate.Value,
-						state.VideoStream == null ? null : state.VideoStream.Codec,
+                        state.VideoStream == null ? (int?)null : state.VideoStream.BitRate,
+                        state.OutputVideoBitrate.Value,
+                        state.VideoStream == null ? null : state.VideoStream.Codec,
                         state.OutputVideoCodec,
-                        request.MaxWidth,
-                        request.MaxHeight);
+                        videoRequest.MaxWidth,
+                        videoRequest.MaxHeight);
 
-                    request.MaxWidth = resolution.MaxWidth;
-                    request.MaxHeight = resolution.MaxHeight;
+                    videoRequest.MaxWidth = resolution.MaxWidth;
+                    videoRequest.MaxHeight = resolution.MaxHeight;
                 }
             }
 
             ApplyDeviceProfileSettings(state);
 
-            TryStreamCopy(state, request);
+            if (videoRequest != null)
+            {
+                TryStreamCopy(state, videoRequest);
+            }
+
+            //state.OutputFilePath = GetOutputFilePath(state);
 
             return state;
         }
@@ -119,7 +138,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
             }
         }
 
-        internal static void AttachMediaStreamInfo(EncodingJob state,
+        internal static void AttachMediaSourceInfo(EncodingJob state,
             MediaSourceInfo mediaSource,
             EncodingJobOptions videoRequest)
         {
@@ -131,11 +150,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
             state.RunTimeTicks = mediaSource.RunTimeTicks;
             state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
 
-            if (mediaSource.ReadAtNativeFramerate)
-            {
-                state.ReadInputAtNativeFramerate = true;
-            }
-
             if (mediaSource.VideoType.HasValue)
             {
                 state.VideoType = mediaSource.VideoType.Value;
@@ -156,6 +170,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
             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))
@@ -165,6 +180,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 state.InputAudioSync = "1";
             }
 
+            if (string.Equals(mediaSource.Container, "wma", StringComparison.OrdinalIgnoreCase))
+            {
+                // Seeing some stuttering when transcoding wma to audio-only HLS
+                state.InputAudioSync = "1";
+            }
+
             var mediaStreams = mediaSource.MediaStreams;
 
             if (videoRequest != null)
@@ -210,19 +231,21 @@ namespace MediaBrowser.MediaEncoding.Encoder
         /// <returns>System.Nullable{VideoCodecs}.</returns>
         private static string InferVideoCodec(string container)
         {
-            if (string.Equals(container, "asf", StringComparison.OrdinalIgnoreCase))
+            var ext = "." + (container ?? string.Empty);
+
+            if (string.Equals(ext, ".asf", StringComparison.OrdinalIgnoreCase))
             {
                 return "wmv";
             }
-            if (string.Equals(container, "webm", StringComparison.OrdinalIgnoreCase))
+            if (string.Equals(ext, ".webm", StringComparison.OrdinalIgnoreCase))
             {
                 return "vpx";
             }
-            if (string.Equals(container, "ogg", StringComparison.OrdinalIgnoreCase) || string.Equals(container, "ogv", StringComparison.OrdinalIgnoreCase))
+            if (string.Equals(ext, ".ogg", StringComparison.OrdinalIgnoreCase) || string.Equals(ext, ".ogv", StringComparison.OrdinalIgnoreCase))
             {
                 return "theora";
             }
-            if (string.Equals(container, "m3u8", StringComparison.OrdinalIgnoreCase) || string.Equals(container, "ts", StringComparison.OrdinalIgnoreCase))
+            if (string.Equals(ext, ".m3u8", StringComparison.OrdinalIgnoreCase) || string.Equals(ext, ".ts", StringComparison.OrdinalIgnoreCase))
             {
                 return "h264";
             }
@@ -232,35 +255,37 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
         private string InferAudioCodec(string container)
         {
-            if (string.Equals(container, "mp3", StringComparison.OrdinalIgnoreCase))
+            var ext = "." + (container ?? string.Empty);
+
+            if (string.Equals(ext, ".mp3", StringComparison.OrdinalIgnoreCase))
             {
                 return "mp3";
             }
-            if (string.Equals(container, "aac", StringComparison.OrdinalIgnoreCase))
+            if (string.Equals(ext, ".aac", StringComparison.OrdinalIgnoreCase))
             {
                 return "aac";
             }
-            if (string.Equals(container, "wma", StringComparison.OrdinalIgnoreCase))
+            if (string.Equals(ext, ".wma", StringComparison.OrdinalIgnoreCase))
             {
                 return "wma";
             }
-            if (string.Equals(container, "ogg", StringComparison.OrdinalIgnoreCase))
+            if (string.Equals(ext, ".ogg", StringComparison.OrdinalIgnoreCase))
             {
                 return "vorbis";
             }
-            if (string.Equals(container, "oga", StringComparison.OrdinalIgnoreCase))
+            if (string.Equals(ext, ".oga", StringComparison.OrdinalIgnoreCase))
             {
                 return "vorbis";
             }
-            if (string.Equals(container, "ogv", StringComparison.OrdinalIgnoreCase))
+            if (string.Equals(ext, ".ogv", StringComparison.OrdinalIgnoreCase))
             {
                 return "vorbis";
             }
-            if (string.Equals(container, "webm", StringComparison.OrdinalIgnoreCase))
+            if (string.Equals(ext, ".webm", StringComparison.OrdinalIgnoreCase))
             {
                 return "vorbis";
             }
-            if (string.Equals(container, "webma", StringComparison.OrdinalIgnoreCase))
+            if (string.Equals(ext, ".webma", StringComparison.OrdinalIgnoreCase))
             {
                 return "vorbis";
             }
@@ -398,15 +423,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
             if (bitrate.HasValue)
             {
-                var hasFixedResolution = state.Options.HasFixedResolution;
-
                 if (string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase))
                 {
-                    if (hasFixedResolution)
-                    {
-                        return string.Format(" -minrate:v ({0}*.90) -maxrate:v ({0}*1.10) -bufsize:v {0} -b:v {0}", bitrate.Value.ToString(UsCulture));
-                    }
-
                     // With vpx when crf is used, b:v becomes a max rate
                     // https://trac.ffmpeg.org/wiki/vpxEncodingGuide. But higher bitrate source files -b:v causes judder so limite the bitrate but dont allow it to "saturate" the bitrate. So dont contrain it down just up.
                     return string.Format(" -maxrate:v {0} -bufsize:v ({0}*2) -b:v {0}", bitrate.Value.ToString(UsCulture));
@@ -417,20 +435,15 @@ namespace MediaBrowser.MediaEncoding.Encoder
                     return string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture));
                 }
 
-                // H264
-                if (hasFixedResolution)
+                // h264
+                if (isHls)
                 {
-                    if (isHls)
-                    {
-                        return string.Format(" -b:v {0} -maxrate ({0}*.80) -bufsize {0}", bitrate.Value.ToString(UsCulture));
-                    }
-
-                    return string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture));
+                    return string.Format(" -b:v {0} -maxrate {0} -bufsize {1}",
+                        bitrate.Value.ToString(UsCulture),
+                        (bitrate.Value * 2).ToString(UsCulture));
                 }
 
-                return string.Format(" -maxrate {0} -bufsize {1}",
-                    bitrate.Value.ToString(UsCulture),
-                    (bitrate.Value * 2).ToString(UsCulture));
+                return string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture));
             }
 
             return string.Empty;
@@ -466,11 +479,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
         /// <summary>
         /// Gets the name of the output audio codec
         /// </summary>
-        /// <param name="request">The request.</param>
+        /// <param name="state">The state.</param>
         /// <returns>System.String.</returns>
-        private string GetAudioCodec(EncodingJobOptions request)
+        internal static string GetAudioEncoder(EncodingJob state)
         {
-            var codec = request.AudioCodec;
+            var codec = state.OutputAudioCodec;
 
             if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase))
             {
@@ -489,40 +502,56 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 return "wmav2";
             }
 
-            return (codec ?? string.Empty).ToLower();
+            return codec.ToLower();
         }
 
         /// <summary>
         /// Gets the name of the output video codec
         /// </summary>
-        /// <param name="request">The request.</param>
+        /// <param name="state">The state.</param>
+        /// <param name="options">The options.</param>
         /// <returns>System.String.</returns>
-        private string GetVideoCodec(EncodingJobOptions request)
+        internal static string GetVideoEncoder(EncodingJob state, EncodingOptions options)
         {
-            var codec = request.VideoCodec;
+            var codec = state.OutputVideoCodec;
 
-            if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase))
+            if (!string.IsNullOrEmpty(codec))
             {
-                return "libx264";
-            }
-            if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase) || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase))
-            {
-                return "libx265";
-            }
-            if (string.Equals(codec, "vpx", StringComparison.OrdinalIgnoreCase))
-            {
-                return "libvpx";
-            }
-            if (string.Equals(codec, "wmv", StringComparison.OrdinalIgnoreCase))
-            {
-                return "wmv2";
+                if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase))
+                {
+                    return GetH264Encoder(state, options);
+                }
+                if (string.Equals(codec, "vpx", StringComparison.OrdinalIgnoreCase))
+                {
+                    return "libvpx";
+                }
+                if (string.Equals(codec, "wmv", StringComparison.OrdinalIgnoreCase))
+                {
+                    return "wmv2";
+                }
+                if (string.Equals(codec, "theora", StringComparison.OrdinalIgnoreCase))
+                {
+                    return "libtheora";
+                }
+
+                return codec.ToLower();
             }
-            if (string.Equals(codec, "theora", StringComparison.OrdinalIgnoreCase))
+
+            return "copy";
+        }
+
+        internal static string GetH264Encoder(EncodingJob state, EncodingOptions options)
+        {
+            if (string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
             {
-                return "libtheora";
+                // It's currently failing on live tv
+                if (state.RunTimeTicks.HasValue)
+                {
+                    return "h264_qsv";
+                }
             }
 
-            return (codec ?? string.Empty).ToLower();
+            return "libx264";
         }
 
         internal static bool CanStreamCopyVideo(EncodingJobOptions request, MediaStream videoStream)

+ 27 - 20
MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs

@@ -21,14 +21,14 @@ namespace MediaBrowser.MediaEncoding.Encoder
         protected override string GetCommandLineArguments(EncodingJob state)
         {
             // Get the output codec name
-            var videoCodec = state.OutputVideoCodec;
+            var videoCodec = EncodingJobFactory.GetVideoEncoder(state, GetEncodingOptions());
 
             var format = string.Empty;
             var keyFrame = string.Empty;
 
-            if (string.Equals(Path.GetExtension(state.OutputFilePath), ".mp4", StringComparison.OrdinalIgnoreCase) &&
-                state.Options.Context == EncodingContext.Streaming)
+            if (string.Equals(Path.GetExtension(state.OutputFilePath), ".mp4", StringComparison.OrdinalIgnoreCase))
             {
+                // Comparison: https://github.com/jansmolders86/mediacenterjs/blob/master/lib/transcoding/desktop.js
                 format = " -f mp4 -movflags frag_keyframe+empty_moov";
             }
 
@@ -53,42 +53,49 @@ namespace MediaBrowser.MediaEncoding.Encoder
         /// Gets video arguments to pass to ffmpeg
         /// </summary>
         /// <param name="state">The state.</param>
-        /// <param name="codec">The video codec.</param>
+        /// <param name="videoCodec">The video codec.</param>
         /// <returns>System.String.</returns>
-        private string GetVideoArguments(EncodingJob state, string codec)
+        private string GetVideoArguments(EncodingJob state, string videoCodec)
         {
-            var args = "-codec:v:0 " + codec;
+            var args = "-codec:v:0 " + videoCodec;
 
             if (state.EnableMpegtsM2TsMode)
             {
                 args += " -mpegts_m2ts_mode 1";
             }
 
-            // See if we can save come cpu cycles by avoiding encoding
-            if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
+            var isOutputMkv = string.Equals(state.Options.OutputContainer, "mkv", StringComparison.OrdinalIgnoreCase);
+
+            if (state.RunTimeTicks.HasValue)
             {
-                return state.VideoStream != null && IsH264(state.VideoStream) && string.Equals(state.Options.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase) ?
-                    args + " -bsf:v h264_mp4toannexb" :
-                    args;
+                //args += " -copyts -avoid_negative_ts disabled -start_at_zero";
             }
 
-            if (state.Options.Context == EncodingContext.Streaming)
+            if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase))
             {
-                var keyFrameArg = string.Format(" -force_key_frames expr:gte(t,n_forced*{0})",
-                    5.ToString(UsCulture));
+                if (state.VideoStream != null && IsH264(state.VideoStream) &&
+                    (string.Equals(state.Options.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase) || isOutputMkv))
+                {
+                    args += " -bsf:v h264_mp4toannexb";
+                }
 
-                args += keyFrameArg;
+                return args;
             }
 
+            var keyFrameArg = string.Format(" -force_key_frames expr:gte(t,n_forced*{0})",
+                5.ToString(UsCulture));
+
+            args += keyFrameArg;
+
             var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream;
 
             // Add resolution params, if specified
             if (!hasGraphicalSubs)
             {
-                args += GetOutputSizeParam(state, codec);
+                args += GetOutputSizeParam(state, videoCodec);
             }
 
-            var qualityParam = GetVideoQualityParam(state, codec, false);
+            var qualityParam = GetVideoQualityParam(state, videoCodec, false);
 
             if (!string.IsNullOrEmpty(qualityParam))
             {
@@ -98,7 +105,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
             // This is for internal graphical subs
             if (hasGraphicalSubs)
             {
-                args += GetGraphicalSubtitleParam(state, codec);
+                args += GetGraphicalSubtitleParam(state, videoCodec);
             }
 
             return args;
@@ -118,11 +125,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
             }
 
             // Get the output codec name
-            var codec = state.OutputAudioCodec;
+            var codec = EncodingJobFactory.GetAudioEncoder(state);
 
             var args = "-codec:a:0 " + codec;
 
-            if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
+            if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
             {
                 return args;
             }

+ 0 - 2
MediaBrowser.Model/Configuration/CinemaModeConfiguration.cs

@@ -16,14 +16,12 @@ namespace MediaBrowser.Model.Configuration
         public bool EnableIntrosFromUpcomingStreamingMovies { get; set; }
 
         public int TrailerLimit { get; set; }
-        public string[] Tags { get; set; }
         
         public CinemaModeConfiguration()
         {
             EnableIntrosParentalControl = true;
             EnableIntrosFromSimilarMovies = true;
             TrailerLimit = 2;
-            Tags = new[] { "thx" };
         }
     }
 }

+ 0 - 2
MediaBrowser.Model/Configuration/ServerConfiguration.cs

@@ -161,8 +161,6 @@ namespace MediaBrowser.Model.Configuration
         /// </summary>
         /// <value>The dashboard source path.</value>
         public string DashboardSourcePath { get; set; }
-
-        public bool MergeMetadataAndImagesByName { get; set; }
         
         /// <summary>
         /// Gets or sets the image saving convention.

+ 1 - 1
MediaBrowser.Model/Dto/MediaSourceInfo.cs

@@ -51,7 +51,7 @@ namespace MediaBrowser.Model.Dto
         public string TranscodingUrl { get; set; }
         public string TranscodingSubProtocol { get; set; }
         public string TranscodingContainer { get; set; }
-
+        
         public MediaSourceInfo()
         {
             Formats = new List<string>();

+ 6 - 1
MediaBrowser.Providers/Manager/ImageSaver.cs

@@ -375,7 +375,12 @@ namespace MediaBrowser.Providers.Manager
             }
 
             string filename;
-            var folderName = item is MusicAlbum || item is MusicArtist ? "folder" : "poster";
+            var folderName = item is MusicAlbum || 
+                item is MusicArtist || 
+                item is PhotoAlbum || 
+                (saveLocally && _config.Configuration.ImageSavingConvention == ImageSavingConvention.Legacy) ? 
+                "folder" : 
+                "poster";
 
             switch (type)
             {

+ 0 - 15
MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs

@@ -35,7 +35,6 @@ namespace MediaBrowser.Server.Implementations.Configuration
         public ServerConfigurationManager(IApplicationPaths applicationPaths, ILogManager logManager, IXmlSerializer xmlSerializer, IFileSystem fileSystem)
             : base(applicationPaths, logManager, xmlSerializer, fileSystem)
         {
-            UpdateItemsByNamePath();
             UpdateMetadataPath();
         }
 
@@ -73,7 +72,6 @@ namespace MediaBrowser.Server.Implementations.Configuration
         /// </summary>
         protected override void OnConfigurationUpdated()
         {
-            UpdateItemsByNamePath();
             UpdateMetadataPath();
 
             base.OnConfigurationUpdated();
@@ -86,19 +84,6 @@ namespace MediaBrowser.Server.Implementations.Configuration
             UpdateTranscodingTempPath();
         }
 
-        /// <summary>
-        /// Updates the items by name path.
-        /// </summary>
-        private void UpdateItemsByNamePath()
-        {
-            if (!Configuration.MergeMetadataAndImagesByName)
-            {
-                ((ServerApplicationPaths)ApplicationPaths).ItemsByNamePath = string.IsNullOrEmpty(Configuration.ItemsByNamePath) ?
-                    null :
-                    Configuration.ItemsByNamePath;
-            }
-        }
-
         /// <summary>
         /// Updates the metadata path.
         /// </summary>

+ 139 - 3
MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs

@@ -17,6 +17,7 @@ using System.Threading;
 using System.Threading.Tasks;
 using CommonIO;
 using MediaBrowser.Common.IO;
+using MoreLinq;
 
 namespace MediaBrowser.Server.Implementations.Intros
 {
@@ -28,8 +29,9 @@ namespace MediaBrowser.Server.Implementations.Intros
         private readonly IConfigurationManager _serverConfig;
         private readonly ILibraryManager _libraryManager;
         private readonly IFileSystem _fileSystem;
+        private readonly IMediaSourceManager _mediaSourceManager;
 
-        public DefaultIntroProvider(ISecurityManager security, IChannelManager channelManager, ILocalizationManager localization, IConfigurationManager serverConfig, ILibraryManager libraryManager, IFileSystem fileSystem)
+        public DefaultIntroProvider(ISecurityManager security, IChannelManager channelManager, ILocalizationManager localization, IConfigurationManager serverConfig, ILibraryManager libraryManager, IFileSystem fileSystem, IMediaSourceManager mediaSourceManager)
         {
             _security = security;
             _channelManager = channelManager;
@@ -37,6 +39,7 @@ namespace MediaBrowser.Server.Implementations.Intros
             _serverConfig = serverConfig;
             _libraryManager = libraryManager;
             _fileSystem = fileSystem;
+            _mediaSourceManager = mediaSourceManager;
         }
 
         public async Task<IEnumerable<IntroInfo>> GetIntros(BaseItem item, User user)
@@ -82,7 +85,7 @@ namespace MediaBrowser.Server.Implementations.Intros
                 {
                     IncludeItemTypes = new[] { typeof(Movie).Name }
 
-                }, new string[]{});
+                }, new string[] { });
 
                 var itemsWithTrailers = inputItems
                     .Where(i =>
@@ -164,6 +167,10 @@ namespace MediaBrowser.Server.Implementations.Intros
                 GetCustomIntros(config) :
                 new List<IntroInfo>();
 
+            var mediaInfoIntros = !string.IsNullOrWhiteSpace(config.MediaInfoIntroPath) ?
+                GetMediaInfoIntros(config, item) :
+                new List<IntroInfo>();
+
             var trailerLimit = config.TrailerLimit;
 
             // Avoid implicitly captured closure
@@ -185,7 +192,8 @@ namespace MediaBrowser.Server.Implementations.Intros
                 .ThenByDescending(i => (i.IsPlayed ? 0 : 1))
                 .Select(i => i.IntroInfo)
                 .Take(trailerLimit)
-                .Concat(customIntros.Take(1));
+                .Concat(customIntros.Take(1))
+                .Concat(mediaInfoIntros);
         }
 
         private bool IsDuplicate(BaseItem playingContent, BaseItem test)
@@ -228,6 +236,134 @@ namespace MediaBrowser.Server.Implementations.Intros
             }
         }
 
+        private IEnumerable<IntroInfo> GetMediaInfoIntros(CinemaModeConfiguration options, BaseItem item)
+        {
+            try
+            {
+                var hasMediaSources = item as IHasMediaSources;
+
+                if (hasMediaSources == null)
+                {
+                    return new List<IntroInfo>();
+                }
+
+                var mediaSource = _mediaSourceManager.GetStaticMediaSources(hasMediaSources, false)
+                    .FirstOrDefault();
+
+                if (mediaSource == null)
+                {
+                    return new List<IntroInfo>();
+                }
+
+                var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);
+                var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
+
+                var allIntros = GetCustomIntroFiles(options, false, true)
+                    .OrderBy(i => Guid.NewGuid())
+                    .Select(i => new IntroInfo
+                    {
+                        Path = i
+
+                    }).ToList();
+
+                var returnResult = new List<IntroInfo>();
+
+                if (videoStream != null)
+                {
+                    returnResult.AddRange(GetMediaInfoIntrosByVideoStream(allIntros, videoStream).Take(1));
+                }
+
+                if (audioStream != null)
+                {
+                    returnResult.AddRange(GetMediaInfoIntrosByAudioStream(allIntros, audioStream).Take(1));
+                }
+
+                returnResult.AddRange(GetMediaInfoIntrosByTags(allIntros, item.Tags).Take(1));
+                
+                return returnResult.DistinctBy(i => i.Path, StringComparer.OrdinalIgnoreCase);
+            }
+            catch (IOException)
+            {
+                return new List<IntroInfo>();
+            }
+        }
+
+        private IEnumerable<IntroInfo> GetMediaInfoIntrosByVideoStream(List<IntroInfo> allIntros, MediaStream stream)
+        {
+            var codec = stream.Codec;
+
+            if (string.IsNullOrWhiteSpace(codec))
+            {
+                return new List<IntroInfo>();
+            }
+
+            return allIntros
+                .Where(i => IsMatch(i.Path, codec));
+        }
+
+        private IEnumerable<IntroInfo> GetMediaInfoIntrosByAudioStream(List<IntroInfo> allIntros, MediaStream stream)
+        {
+            var codec = stream.Codec;
+
+            if (string.IsNullOrWhiteSpace(codec))
+            {
+                return new List<IntroInfo>();
+            }
+
+            return allIntros
+                .Where(i => IsAudioMatch(i.Path, stream));
+        }
+
+        private IEnumerable<IntroInfo> GetMediaInfoIntrosByTags(List<IntroInfo> allIntros, List<string> tags)
+        {
+            return allIntros
+                .Where(i => tags.Any(t => IsMatch(i.Path, t)));
+        }
+
+        private bool IsMatch(string file, string attribute)
+        {
+            var filename = Path.GetFileNameWithoutExtension(file) ?? string.Empty;
+            filename = Normalize(filename);
+
+            if (string.IsNullOrWhiteSpace(filename))
+            {
+                return false;
+            }
+
+            attribute = Normalize(attribute);
+            if (string.IsNullOrWhiteSpace(attribute))
+            {
+                return false;
+            }
+
+            return string.Equals(filename, attribute, StringComparison.OrdinalIgnoreCase);
+        }
+
+        private string Normalize(string value)
+        {
+            return value;
+        }
+
+        private bool IsAudioMatch(string path, MediaStream stream)
+        {
+            if (!string.IsNullOrWhiteSpace(stream.Codec))
+            {
+                if (IsMatch(path, stream.Codec))
+                {
+                    return true;
+                }
+            }
+            if (!string.IsNullOrWhiteSpace(stream.Profile))
+            {
+                if (IsMatch(path, stream.Profile))
+                {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
         private IEnumerable<string> GetCustomIntroFiles(CinemaModeConfiguration options, bool enableCustomIntros, bool enableMediaInfoIntros)
         {
             var list = new List<string>();

+ 4 - 0
MediaBrowser.Server.Implementations/Library/LibraryManager.cs

@@ -1536,6 +1536,10 @@ namespace MediaBrowser.Server.Implementations.Library
                         {
                             video = dbItem;
                         }
+                        else
+                        {
+                            return null;
+                        }
                     }
                 }
                 catch (Exception ex)

+ 8 - 3
MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs

@@ -742,7 +742,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
 
             try
             {
-                var result = await GetChannelStreamInternal(timer.ChannelId, null, CancellationToken.None);
+                var result = await GetChannelStreamInternal(timer.ChannelId, null, CancellationToken.None).ConfigureAwait(false);
                 var mediaStreamInfo = result.Item1;
                 var isResourceOpen = true;
 
@@ -771,14 +771,19 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
                     var linkedToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
                     httpRequestOptions.CancellationToken = linkedToken;
                     _logger.Info("Writing file to path: " + recordPath);
-                    using (var response = await _httpClient.SendAsync(httpRequestOptions, "GET"))
+                    _logger.Info("Opening recording stream from tuner provider");
+                    using (var response = await _httpClient.SendAsync(httpRequestOptions, "GET").ConfigureAwait(false))
                     {
+                        _logger.Info("Opened recording stream from tuner provider");
+                        
                         using (var output = _fileSystem.GetFileStream(recordPath, FileMode.Create, FileAccess.Write, FileShare.Read))
                         {
                             result.Item2.Release();
                             isResourceOpen = false;
 
-                            await response.Content.CopyToAsync(output, StreamDefaults.DefaultCopyToBufferSize, linkedToken);
+                            _logger.Info("Copying recording stream to file stream");
+                            
+                            await response.Content.CopyToAsync(output, StreamDefaults.DefaultCopyToBufferSize, linkedToken).ConfigureAwait(false);
                         }
                     }
 

+ 1 - 1
MediaBrowser.Server.Implementations/Localization/Core/de.json

@@ -1,5 +1,5 @@
 {
-    "DbUpgradeMessage": "Please wait while your Emby Server database is upgraded. {0}% complete.",
+    "DbUpgradeMessage": "Bitte warten Sie w\u00e4hrend die Emby Datenbank aktualisiert wird. {0}% verarbeitet.",
     "AppDeviceValues": "App: {0}, Ger\u00e4t: {1}",
     "UserDownloadingItemWithValues": "{0} l\u00e4dt {1} herunter",
     "FolderTypeMixed": "Gemischte Inhalte",

+ 1 - 1
MediaBrowser.Server.Implementations/Localization/Core/es-MX.json

@@ -1,5 +1,5 @@
 {
-    "DbUpgradeMessage": "Please wait while your Emby Server database is upgraded. {0}% complete.",
+    "DbUpgradeMessage": "Por favor espere mientras la base de datos de su Servidor Emby es actualizada. {0}% completo.",
     "AppDeviceValues": "App: {0}, Dispositivo: {1}",
     "UserDownloadingItemWithValues": "{0} esta descargando {1}",
     "FolderTypeMixed": "Contenido mezclado",

+ 1 - 1
MediaBrowser.Server.Implementations/Localization/Core/fr.json

@@ -1,5 +1,5 @@
 {
-    "DbUpgradeMessage": "Please wait while your Emby Server database is upgraded. {0}% complete.",
+    "DbUpgradeMessage": "Veuillez patienter pendant que la base de donn\u00e9e de votre Emby Serveur se met \u00e0 jour. Termin\u00e9e \u00e0 {0}%.",
     "AppDeviceValues": "Application : {0}, Appareil: {1}",
     "UserDownloadingItemWithValues": "{0} est en train de t\u00e9l\u00e9charger {1}",
     "FolderTypeMixed": "Contenus m\u00e9lang\u00e9s",

+ 1 - 1
MediaBrowser.Server.Implementations/Localization/Core/kk.json

@@ -1,5 +1,5 @@
 {
-    "DbUpgradeMessage": "Please wait while your Emby Server database is upgraded. {0}% complete.",
+    "DbUpgradeMessage": "Emby Server \u0434\u0435\u0440\u0435\u043a\u049b\u043e\u0440\u044b\u04a3\u044b\u0437\u0434\u044b\u04a3 \u0436\u0430\u04a3\u0493\u044b\u0440\u0442\u044b\u043b\u0443\u044b\u043d \u043a\u04af\u0442\u0435 \u0442\u04b1\u0440\u044b\u04a3\u044b\u0437. {0} % \u0430\u044f\u049b\u0442\u0430\u043b\u0434\u044b.",
     "AppDeviceValues": "\u049a\u043e\u043b\u0434\u0430\u043d\u0431\u0430: {0}, \u049a\u04b1\u0440\u044b\u043b\u0493\u044b: {1}",
     "UserDownloadingItemWithValues": "{0} \u043c\u044b\u043d\u0430\u043d\u044b \u0436\u04af\u043a\u0442\u0435\u043f \u0430\u043b\u0443\u0434\u0430: {1}",
     "FolderTypeMixed": "\u0410\u0440\u0430\u043b\u0430\u0441 \u043c\u0430\u0437\u043c\u04b1\u043d",

+ 1 - 1
MediaBrowser.Server.Implementations/Localization/Core/nl.json

@@ -1,5 +1,5 @@
 {
-    "DbUpgradeMessage": "Please wait while your Emby Server database is upgraded. {0}% complete.",
+    "DbUpgradeMessage": "Even geduld svp terwijl de Emby Server database ge-upgrade wordt. {0}% gereed.",
     "AppDeviceValues": "App: {0}, Apparaat: {1}",
     "UserDownloadingItemWithValues": "{0} download {1}",
     "FolderTypeMixed": "Gemengde inhoud",

+ 1 - 1
MediaBrowser.Server.Implementations/Localization/Core/pt-BR.json

@@ -1,5 +1,5 @@
 {
-    "DbUpgradeMessage": "Please wait while your Emby Server database is upgraded. {0}% complete.",
+    "DbUpgradeMessage": "Por favor, aguarde enquanto a base de dados do Servidor Emby \u00e9 atualizada. {0}% completado.",
     "AppDeviceValues": "App: {0}, Dispositivo: {1}",
     "UserDownloadingItemWithValues": "{0} est\u00e1 fazendo download de {1}",
     "FolderTypeMixed": "Conte\u00fado misto",

+ 1 - 1
MediaBrowser.Server.Implementations/Localization/Core/ru.json

@@ -1,5 +1,5 @@
 {
-    "DbUpgradeMessage": "Please wait while your Emby Server database is upgraded. {0}% complete.",
+    "DbUpgradeMessage": "\u041f\u043e\u0434\u043e\u0436\u0434\u0438\u0442\u0435, \u043f\u043e\u043a\u0430 \u0431\u0430\u0437\u0430 \u0434\u0430\u043d\u043d\u044b\u0445 \u043d\u0430 \u0432\u0430\u0448\u0435\u043c Emby Server \u043c\u043e\u0434\u0435\u0440\u043d\u0438\u0437\u0438\u0440\u0443\u0435\u0442\u0441\u044f. {0} % \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043e.",
     "AppDeviceValues": "\u041f\u0440\u0438\u043b.: {0}, \u0423\u0441\u0442\u0440.: {1}",
     "UserDownloadingItemWithValues": "{0} \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u0442 {1}",
     "FolderTypeMixed": "\u0420\u0430\u0437\u043d\u043e\u0442\u0438\u043f\u043d\u043e\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435",

+ 1 - 0
MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs

@@ -11,6 +11,7 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
+using MediaBrowser.Model.MediaInfo;
 
 namespace MediaBrowser.Server.Implementations.Sync
 {

+ 5 - 0
MediaBrowser.WebDashboard/Api/DashboardService.cs

@@ -356,6 +356,11 @@ namespace MediaBrowser.WebDashboard.Api
             DeleteFoldersByName(Path.Combine(bowerPath, "Sortable"), "st");
             DeleteFoldersByName(Path.Combine(bowerPath, "Swiper"), "src");
 
+            _fileSystem.DeleteDirectory(Path.Combine(bowerPath, "marked"), true);
+            _fileSystem.DeleteDirectory(Path.Combine(bowerPath, "marked-element"), true);
+            _fileSystem.DeleteDirectory(Path.Combine(bowerPath, "prism"), true);
+            _fileSystem.DeleteDirectory(Path.Combine(bowerPath, "prism-element"), true);
+           
             if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase))
             {
                 // Delete things that are unneeded in an attempt to keep the output as trim as possible

+ 3 - 3
MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj

@@ -107,6 +107,9 @@
     <Content Include="dashboard-ui\components\recordingcreator\recordingcreator.template.html">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
+    <Content Include="dashboard-ui\components\remotecontrol.js">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
     <Content Include="dashboard-ui\components\remotecontrolautoplay.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
@@ -161,9 +164,6 @@
     <Content Include="dashboard-ui\components\playlisteditor\playlisteditor.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
-    <Content Include="dashboard-ui\components\prompt.js">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
     <Content Include="dashboard-ui\components\tvguide\tvguide.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>