Explorar o código

rework text subtitles

Luke Pulverenti %!s(int64=11) %!d(string=hai) anos
pai
achega
ec4000404d

+ 31 - 10
MediaBrowser.Api/LiveTv/LiveTvService.cs

@@ -1,11 +1,11 @@
-using System.Globalization;
-using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Model.LiveTv;
 using MediaBrowser.Model.LiveTv;
 using MediaBrowser.Model.Querying;
 using MediaBrowser.Model.Querying;
 using ServiceStack;
 using ServiceStack;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.Globalization;
 using System.Linq;
 using System.Linq;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
@@ -27,6 +27,20 @@ namespace MediaBrowser.Api.LiveTv
 
 
         [ApiMember(Name = "UserId", Description = "Optional filter by user and attach user data.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
         [ApiMember(Name = "UserId", Description = "Optional filter by user and attach user data.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
         public string UserId { get; set; }
         public string UserId { get; set; }
+
+        /// <summary>
+        /// Skips over a given number of items within the results. Use for paging.
+        /// </summary>
+        /// <value>The start index.</value>
+        [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
+        public int? StartIndex { get; set; }
+
+        /// <summary>
+        /// The maximum number of items to return
+        /// </summary>
+        /// <value>The limit.</value>
+        [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
+        public int? Limit { get; set; }
     }
     }
 
 
     [Route("/LiveTv/Channels/{Id}", "GET")]
     [Route("/LiveTv/Channels/{Id}", "GET")]
@@ -116,26 +130,26 @@ namespace MediaBrowser.Api.LiveTv
         public string SeriesTimerId { get; set; }
         public string SeriesTimerId { get; set; }
     }
     }
 
 
-    [Route("/LiveTv/Programs", "GET")]
+    [Route("/LiveTv/Programs", "GET,POST")]
     [Api(Description = "Gets available live tv epgs..")]
     [Api(Description = "Gets available live tv epgs..")]
     public class GetPrograms : IReturn<QueryResult<ProgramInfoDto>>
     public class GetPrograms : IReturn<QueryResult<ProgramInfoDto>>
     {
     {
-        [ApiMember(Name = "ChannelIds", Description = "The channels to return guide information for.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        [ApiMember(Name = "ChannelIds", Description = "The channels to return guide information for.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")]
         public string ChannelIds { get; set; }
         public string ChannelIds { get; set; }
 
 
-        [ApiMember(Name = "UserId", Description = "Optional filter by user id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        [ApiMember(Name = "UserId", Description = "Optional filter by user id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")]
         public string UserId { get; set; }
         public string UserId { get; set; }
 
 
-        [ApiMember(Name = "MinStartDate", Description = "Optional. The minimum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
+        [ApiMember(Name = "MinStartDate", Description = "Optional. The minimum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")]
         public string MinStartDate { get; set; }
         public string MinStartDate { get; set; }
 
 
-        [ApiMember(Name = "MaxStartDate", Description = "Optional. The maximum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
+        [ApiMember(Name = "MaxStartDate", Description = "Optional. The maximum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")]
         public string MaxStartDate { get; set; }
         public string MaxStartDate { get; set; }
 
 
-        [ApiMember(Name = "MinEndDate", Description = "Optional. The minimum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
+        [ApiMember(Name = "MinEndDate", Description = "Optional. The minimum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")]
         public string MinEndDate { get; set; }
         public string MinEndDate { get; set; }
 
 
-        [ApiMember(Name = "MaxEndDate", Description = "Optional. The maximum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
+        [ApiMember(Name = "MaxEndDate", Description = "Optional. The maximum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")]
         public string MaxEndDate { get; set; }
         public string MaxEndDate { get; set; }
     }
     }
 
 
@@ -260,7 +274,9 @@ namespace MediaBrowser.Api.LiveTv
             var result = _liveTvManager.GetChannels(new ChannelQuery
             var result = _liveTvManager.GetChannels(new ChannelQuery
             {
             {
                 ChannelType = request.Type,
                 ChannelType = request.Type,
-                UserId = request.UserId
+                UserId = request.UserId,
+                StartIndex = request.StartIndex,
+                Limit = request.Limit
 
 
             }, CancellationToken.None).Result;
             }, CancellationToken.None).Result;
 
 
@@ -309,6 +325,11 @@ namespace MediaBrowser.Api.LiveTv
             return ToOptimizedResult(result);
             return ToOptimizedResult(result);
         }
         }
 
 
+        public object Post(GetPrograms request)
+        {
+            return Get(request);
+        }
+
         public object Get(GetRecordings request)
         public object Get(GetRecordings request)
         {
         {
             var result = _liveTvManager.GetRecordings(new RecordingQuery
             var result = _liveTvManager.GetRecordings(new RecordingQuery

+ 111 - 49
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -253,25 +253,44 @@ namespace MediaBrowser.Api.Playback
             return returnFirstIfNoIndex ? streams.FirstOrDefault() : null;
             return returnFirstIfNoIndex ? streams.FirstOrDefault() : null;
         }
         }
 
 
+        protected EncodingQuality GetQualitySetting()
+        {
+            var quality = ServerConfigurationManager.Configuration.MediaEncodingQuality;
+
+            if (quality == EncodingQuality.Auto)
+            {
+                var cpuCount = Environment.ProcessorCount;
+
+                if (cpuCount >= 4)
+                {
+                    return EncodingQuality.HighQuality;
+                }
+
+                return EncodingQuality.HighSpeed;
+            }
+
+            return quality;
+        }
+
         /// <summary>
         /// <summary>
         /// Gets the number of threads.
         /// Gets the number of threads.
         /// </summary>
         /// </summary>
         /// <returns>System.Int32.</returns>
         /// <returns>System.Int32.</returns>
         /// <exception cref="System.Exception">Unrecognized MediaEncodingQuality value.</exception>
         /// <exception cref="System.Exception">Unrecognized MediaEncodingQuality value.</exception>
-        protected int GetNumberOfThreads()
+        protected int GetNumberOfThreads(bool isWebm)
         {
         {
-            var quality = ServerConfigurationManager.Configuration.MediaEncodingQuality;
+            // Webm: http://www.webmproject.org/docs/encoder-parameters/
+            // The decoder will usually automatically use an appropriate number of threads according to how many cores are available but it can only use multiple threads 
+            // for the coefficient data if the encoder selected --token-parts > 0 at encode time.
 
 
-            switch (quality)
+            switch (GetQualitySetting())
             {
             {
-                case EncodingQuality.Auto:
-                    return 0;
                 case EncodingQuality.HighSpeed:
                 case EncodingQuality.HighSpeed:
                     return 2;
                     return 2;
                 case EncodingQuality.HighQuality:
                 case EncodingQuality.HighQuality:
-                    return 2;
+                    return isWebm ? Math.Min(3, Environment.ProcessorCount - 1) : 2;
                 case EncodingQuality.MaxQuality:
                 case EncodingQuality.MaxQuality:
-                    return 0;
+                    return isWebm ? Math.Max(2, Environment.ProcessorCount - 1) : 0;
                 default:
                 default:
                     throw new Exception("Unrecognized MediaEncodingQuality value.");
                     throw new Exception("Unrecognized MediaEncodingQuality value.");
             }
             }
@@ -285,30 +304,74 @@ namespace MediaBrowser.Api.Playback
         /// <returns>System.String.</returns>
         /// <returns>System.String.</returns>
         protected string GetVideoQualityParam(StreamState state, string videoCodec)
         protected string GetVideoQualityParam(StreamState state, string videoCodec)
         {
         {
-            var args = string.Empty;
-
             // webm
             // webm
             if (videoCodec.Equals("libvpx", StringComparison.OrdinalIgnoreCase))
             if (videoCodec.Equals("libvpx", StringComparison.OrdinalIgnoreCase))
             {
             {
-                args = "-speed 16 -quality good -profile:v 0 -slices 8";
+                // http://www.webmproject.org/docs/encoder-parameters/
+                return "-speed 16 -quality good -profile:v 0 -slices 8";
             }
             }
 
 
             // asf/wmv
             // asf/wmv
-            else if (videoCodec.Equals("wmv2", StringComparison.OrdinalIgnoreCase))
+            if (videoCodec.Equals("wmv2", StringComparison.OrdinalIgnoreCase))
+            {
+                return "-g 100 -qmax 15";
+            }
+
+            if (videoCodec.Equals("libx264", StringComparison.OrdinalIgnoreCase))
+            {
+                return "-preset superfast";
+            }
+
+            if (videoCodec.Equals("mpeg4", StringComparison.OrdinalIgnoreCase))
             {
             {
-                args = "-g 100 -qmax 15";
+                return "-mbd rd -flags +mv4+aic -trellis 2 -cmp 2 -subcmp 2 -bf 2";
             }
             }
 
 
-            else if (videoCodec.Equals("libx264", StringComparison.OrdinalIgnoreCase))
+            return string.Empty;
+        }
+
+        protected string GetAudioFilterParam(StreamState state, bool isHls)
+        {
+            var volParam = string.Empty;
+            var audioSampleRate = string.Empty;
+
+            var channels = GetNumAudioChannelsParam(state.Request, state.AudioStream);
+            
+            // Boost volume to 200% when downsampling from 6ch to 2ch
+            if (channels.HasValue && channels.Value <= 2 && state.AudioStream.Channels.HasValue && state.AudioStream.Channels.Value > 5)
             {
             {
-                args = "-preset superfast";
+                volParam = ",volume=2.000000";
             }
             }
-            else if (videoCodec.Equals("mpeg4", StringComparison.OrdinalIgnoreCase))
+
+            if (state.Request.AudioSampleRate.HasValue)
             {
             {
-                args = "-mbd rd -flags +mv4+aic -trellis 2 -cmp 2 -subcmp 2 -bf 2";
+                audioSampleRate = state.Request.AudioSampleRate.Value + ":";
             }
             }
 
 
-            return args.Trim();
+            var adelay = isHls ? "adelay=1," : string.Empty;
+
+            var pts = string.Empty;
+
+            if (state.SubtitleStream != null)
+            {
+                if (state.SubtitleStream.Codec.IndexOf("srt", StringComparison.OrdinalIgnoreCase) != -1 ||
+                   state.SubtitleStream.Codec.IndexOf("subrip", StringComparison.OrdinalIgnoreCase) != -1 ||
+                   string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase) ||
+                   string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase))
+                {
+                    var seconds = TimeSpan.FromTicks(state.Request.StartTimeTicks ?? 0).TotalSeconds;
+
+                    pts = string.Format(",asetpts=PTS-{0}/TB",
+                Math.Round(seconds).ToString(UsCulture));
+                }
+            }
+
+            return string.Format("-af \"{0}aresample={1}async=1{2}{3}\"", 
+
+                adelay,
+                audioSampleRate, 
+                volParam,
+                pts);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -323,6 +386,7 @@ namespace MediaBrowser.Api.Playback
             // http://sonnati.wordpress.com/2012/10/19/ffmpeg-the-swiss-army-knife-of-internet-streaming-part-vi/
             // http://sonnati.wordpress.com/2012/10/19/ffmpeg-the-swiss-army-knife-of-internet-streaming-part-vi/
 
 
             var assSubtitleParam = string.Empty;
             var assSubtitleParam = string.Empty;
+            var copyTsParam = string.Empty;
 
 
             var request = state.VideoRequest;
             var request = state.VideoRequest;
 
 
@@ -333,7 +397,8 @@ namespace MediaBrowser.Api.Playback
                     string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase) ||
                     string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase) ||
                     string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase))
                     string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase))
                 {
                 {
-                    assSubtitleParam = GetTextSubtitleParam(state, request.StartTimeTicks, performTextSubtitleConversion);
+                    assSubtitleParam = GetTextSubtitleParam(state, performTextSubtitleConversion);
+                    copyTsParam = " -copyts";
                 }
                 }
             }
             }
 
 
@@ -343,7 +408,7 @@ namespace MediaBrowser.Api.Playback
                 var widthParam = request.Width.Value.ToString(UsCulture);
                 var widthParam = request.Width.Value.ToString(UsCulture);
                 var heightParam = request.Height.Value.ToString(UsCulture);
                 var heightParam = request.Height.Value.ToString(UsCulture);
 
 
-                return string.Format(" -vf \"scale=trunc({0}/2)*2:trunc({1}/2)*2{2}\"", widthParam, heightParam, assSubtitleParam);
+                return string.Format("{3} -vf \"scale=trunc({0}/2)*2:trunc({1}/2)*2{2}\"", widthParam, heightParam, assSubtitleParam, copyTsParam);
             }
             }
 
 
             var isH264Output = outputVideoCodec.Equals("libx264", StringComparison.OrdinalIgnoreCase);
             var isH264Output = outputVideoCodec.Equals("libx264", StringComparison.OrdinalIgnoreCase);
@@ -354,8 +419,8 @@ namespace MediaBrowser.Api.Playback
                 var widthParam = request.Width.Value.ToString(UsCulture);
                 var widthParam = request.Width.Value.ToString(UsCulture);
 
 
                 return isH264Output ?
                 return isH264Output ?
-                    string.Format(" -vf \"scale={0}:trunc(ow/a/2)*2{1}\"", widthParam, assSubtitleParam) :
-                    string.Format(" -vf \"scale={0}:-1{1}\"", widthParam, assSubtitleParam);
+                    string.Format("{2} -vf \"scale={0}:trunc(ow/a/2)*2{1}\"", widthParam, assSubtitleParam, copyTsParam) :
+                    string.Format("{2} -vf \"scale={0}:-1{1}\"", widthParam, assSubtitleParam, copyTsParam);
             }
             }
 
 
             // If a fixed height was requested
             // If a fixed height was requested
@@ -364,8 +429,8 @@ namespace MediaBrowser.Api.Playback
                 var heightParam = request.Height.Value.ToString(UsCulture);
                 var heightParam = request.Height.Value.ToString(UsCulture);
 
 
                 return isH264Output ?
                 return isH264Output ?
-                    string.Format(" -vf \"scale=trunc(oh*a*2)/2:{0}{1}\"", heightParam, assSubtitleParam) :
-                    string.Format(" -vf \"scale=-1:{0}{1}\"", heightParam, assSubtitleParam);
+                    string.Format("{2} -vf \"scale=trunc(oh*a*2)/2:{0}{1}\"", heightParam, assSubtitleParam, copyTsParam) :
+                    string.Format("{2} -vf \"scale=-1:{0}{1}\"", heightParam, assSubtitleParam, copyTsParam);
             }
             }
 
 
             // If a max width was requested
             // If a max width was requested
@@ -374,8 +439,8 @@ namespace MediaBrowser.Api.Playback
                 var maxWidthParam = request.MaxWidth.Value.ToString(UsCulture);
                 var maxWidthParam = request.MaxWidth.Value.ToString(UsCulture);
 
 
                 return isH264Output ?
                 return isH264Output ?
-                    string.Format(" -vf \"scale=min(iw\\,{0}):trunc(ow/a/2)*2{1}\"", maxWidthParam, assSubtitleParam) :
-                    string.Format(" -vf \"scale=min(iw\\,{0}):-1{1}\"", maxWidthParam, assSubtitleParam);
+                    string.Format("{2} -vf \"scale=min(iw\\,{0}):trunc(ow/a/2)*2{1}\"", maxWidthParam, assSubtitleParam, copyTsParam) :
+                    string.Format("{2} -vf \"scale=min(iw\\,{0}):-1{1}\"", maxWidthParam, assSubtitleParam, copyTsParam);
             }
             }
 
 
             // If a max height was requested
             // If a max height was requested
@@ -384,8 +449,8 @@ namespace MediaBrowser.Api.Playback
                 var maxHeightParam = request.MaxHeight.Value.ToString(UsCulture);
                 var maxHeightParam = request.MaxHeight.Value.ToString(UsCulture);
 
 
                 return isH264Output ?
                 return isH264Output ?
-                    string.Format(" -vf \"scale=trunc(oh*a*2)/2:min(ih\\,{0}){1}\"", maxHeightParam, assSubtitleParam) :
-                    string.Format(" -vf \"scale=-1:min(ih\\,{0}){1}\"", maxHeightParam, assSubtitleParam);
+                    string.Format("{2} -vf \"scale=trunc(oh*a*2)/2:min(ih\\,{0}){1}\"", maxHeightParam, assSubtitleParam, copyTsParam) :
+                    string.Format("{2} -vf \"scale=-1:min(ih\\,{0}){1}\"", maxHeightParam, assSubtitleParam, copyTsParam);
             }
             }
 
 
             if (state.VideoStream == null)
             if (state.VideoStream == null)
@@ -408,45 +473,45 @@ namespace MediaBrowser.Api.Playback
                 var widthParam = outputSize.Width.ToString(UsCulture);
                 var widthParam = outputSize.Width.ToString(UsCulture);
                 var heightParam = outputSize.Height.ToString(UsCulture);
                 var heightParam = outputSize.Height.ToString(UsCulture);
 
 
-                return string.Format(" -vf \"scale=trunc({0}/2)*2:trunc({1}/2)*2{2}\"", widthParam, heightParam, assSubtitleParam);
+                return string.Format("{3} -vf \"scale=trunc({0}/2)*2:trunc({1}/2)*2{2}\"", widthParam, heightParam, assSubtitleParam, copyTsParam);
             }
             }
 
 
             // Otherwise use -vf scale since ffmpeg will ensure internally that the aspect ratio is preserved
             // Otherwise use -vf scale since ffmpeg will ensure internally that the aspect ratio is preserved
-            return string.Format(" -vf \"scale={0}:-1{1}\"", Convert.ToInt32(outputSize.Width), assSubtitleParam);
+            return string.Format("{2} -vf \"scale={0}:-1{1}\"", Convert.ToInt32(outputSize.Width), assSubtitleParam, copyTsParam);
         }
         }
 
 
         /// <summary>
         /// <summary>
         /// Gets the text subtitle param.
         /// Gets the text subtitle param.
         /// </summary>
         /// </summary>
         /// <param name="state">The state.</param>
         /// <param name="state">The state.</param>
-        /// <param name="startTimeTicks">The start time ticks.</param>
         /// <param name="performConversion">if set to <c>true</c> [perform conversion].</param>
         /// <param name="performConversion">if set to <c>true</c> [perform conversion].</param>
         /// <returns>System.String.</returns>
         /// <returns>System.String.</returns>
-        protected string GetTextSubtitleParam(StreamState state, long? startTimeTicks, bool performConversion)
+        protected string GetTextSubtitleParam(StreamState state, bool performConversion)
         {
         {
-            var path = state.SubtitleStream.IsExternal ? GetConvertedAssPath(state.MediaPath, state.SubtitleStream, startTimeTicks, performConversion) :
-                GetExtractedAssPath(state, startTimeTicks, performConversion);
+            var path = state.SubtitleStream.IsExternal ? GetConvertedAssPath(state.MediaPath, state.SubtitleStream, performConversion) :
+                GetExtractedAssPath(state, performConversion);
 
 
             if (string.IsNullOrEmpty(path))
             if (string.IsNullOrEmpty(path))
             {
             {
                 return string.Empty;
                 return string.Empty;
             }
             }
 
 
-            return string.Format(",ass='{0}'", path.Replace('\\', '/').Replace(":/", "\\:/"));
+            var seconds = TimeSpan.FromTicks(state.Request.StartTimeTicks ?? 0).TotalSeconds;
+
+            return string.Format(",ass='{0}',setpts=PTS -{1}/TB", 
+                path.Replace('\\', '/').Replace(":/", "\\:/"),
+                Math.Round(seconds).ToString(UsCulture));
         }
         }
 
 
         /// <summary>
         /// <summary>
         /// Gets the extracted ass path.
         /// Gets the extracted ass path.
         /// </summary>
         /// </summary>
         /// <param name="state">The state.</param>
         /// <param name="state">The state.</param>
-        /// <param name="startTimeTicks">The start time ticks.</param>
         /// <param name="performConversion">if set to <c>true</c> [perform conversion].</param>
         /// <param name="performConversion">if set to <c>true</c> [perform conversion].</param>
         /// <returns>System.String.</returns>
         /// <returns>System.String.</returns>
-        private string GetExtractedAssPath(StreamState state, long? startTimeTicks, bool performConversion)
+        private string GetExtractedAssPath(StreamState state, bool performConversion)
         {
         {
-            var offset = TimeSpan.FromTicks(startTimeTicks ?? 0);
-
-            var path = FFMpegManager.Instance.GetSubtitleCachePath(state.MediaPath, state.SubtitleStream, offset, ".ass");
+            var path = FFMpegManager.Instance.GetSubtitleCachePath(state.MediaPath, state.SubtitleStream, ".ass");
 
 
             if (performConversion)
             if (performConversion)
             {
             {
@@ -460,7 +525,7 @@ namespace MediaBrowser.Api.Playback
 
 
                     Directory.CreateDirectory(parentPath);
                     Directory.CreateDirectory(parentPath);
 
 
-                    var task = MediaEncoder.ExtractTextSubtitle(inputPath, type, state.SubtitleStream.Index, offset, path, CancellationToken.None);
+                    var task = MediaEncoder.ExtractTextSubtitle(inputPath, type, state.SubtitleStream.Index, path, CancellationToken.None);
 
 
                     Task.WaitAll(task);
                     Task.WaitAll(task);
                 }
                 }
@@ -478,14 +543,11 @@ namespace MediaBrowser.Api.Playback
         /// </summary>
         /// </summary>
         /// <param name="mediaPath">The media path.</param>
         /// <param name="mediaPath">The media path.</param>
         /// <param name="subtitleStream">The subtitle stream.</param>
         /// <param name="subtitleStream">The subtitle stream.</param>
-        /// <param name="startTimeTicks">The start time ticks.</param>
         /// <param name="performConversion">if set to <c>true</c> [perform conversion].</param>
         /// <param name="performConversion">if set to <c>true</c> [perform conversion].</param>
         /// <returns>System.String.</returns>
         /// <returns>System.String.</returns>
-        private string GetConvertedAssPath(string mediaPath, MediaStream subtitleStream, long? startTimeTicks, bool performConversion)
+        private string GetConvertedAssPath(string mediaPath, MediaStream subtitleStream, bool performConversion)
         {
         {
-            var offset = TimeSpan.FromTicks(startTimeTicks ?? 0);
-
-            var path = FFMpegManager.Instance.GetSubtitleCachePath(mediaPath, subtitleStream, offset, ".ass");
+            var path = FFMpegManager.Instance.GetSubtitleCachePath(mediaPath, subtitleStream, ".ass");
 
 
             if (performConversion)
             if (performConversion)
             {
             {
@@ -495,7 +557,7 @@ namespace MediaBrowser.Api.Playback
 
 
                     Directory.CreateDirectory(parentPath);
                     Directory.CreateDirectory(parentPath);
 
 
-                    var task = MediaEncoder.ConvertTextSubtitleToAss(subtitleStream.Path, path, subtitleStream.Language, offset, CancellationToken.None);
+                    var task = MediaEncoder.ConvertTextSubtitleToAss(subtitleStream.Path, path, subtitleStream.Language, CancellationToken.None);
 
 
                     Task.WaitAll(task);
                     Task.WaitAll(task);
                 }
                 }
@@ -534,9 +596,9 @@ namespace MediaBrowser.Api.Playback
                 videoSizeParam = string.Format(",scale={0}:{1}", state.VideoStream.Width.Value.ToString(UsCulture), state.VideoStream.Height.Value.ToString(UsCulture));
                 videoSizeParam = string.Format(",scale={0}:{1}", state.VideoStream.Width.Value.ToString(UsCulture), state.VideoStream.Height.Value.ToString(UsCulture));
             }
             }
 
 
-            return string.Format(" -filter_complex \"[0:{0}]format=yuva444p{3},lut=u=128:v=128:y=gammaval(.3)[sub] ; [0:{1}] [sub] overlay{2}\"", 
-                state.SubtitleStream.Index, 
-                state.VideoStream.Index, 
+            return string.Format(" -filter_complex \"[0:{0}]format=yuva444p{3},lut=u=128:v=128:y=gammaval(.3)[sub] ; [0:{1}] [sub] overlay{2}\"",
+                state.SubtitleStream.Index,
+                state.VideoStream.Index,
                 outputSizeParam,
                 outputSizeParam,
                 videoSizeParam);
                 videoSizeParam);
         }
         }

+ 3 - 3
MediaBrowser.Api/Playback/Hls/BaseHlsService.cs

@@ -113,7 +113,7 @@ namespace MediaBrowser.Api.Playback.Hls
             if (isPlaylistNewlyCreated)
             if (isPlaylistNewlyCreated)
             {
             {
                 var minimumSegmentCount = 3;
                 var minimumSegmentCount = 3;
-                var quality = ServerConfigurationManager.Configuration.MediaEncodingQuality;
+                var quality = GetQualitySetting();
 
 
                 if (quality == EncodingQuality.HighSpeed || quality == EncodingQuality.HighQuality)
                 if (quality == EncodingQuality.HighSpeed || quality == EncodingQuality.HighQuality)
                 {
                 {
@@ -267,9 +267,9 @@ namespace MediaBrowser.Api.Playback.Hls
 
 
             var itsOffset = itsOffsetMs == 0 ? string.Empty : string.Format("-itsoffset {0} ", TimeSpan.FromMilliseconds(itsOffsetMs).TotalSeconds);
             var itsOffset = itsOffsetMs == 0 ? string.Empty : string.Format("-itsoffset {0} ", TimeSpan.FromMilliseconds(itsOffsetMs).TotalSeconds);
 
 
-            var threads = GetNumberOfThreads();
+            var threads = GetNumberOfThreads(false);
 
 
-            var args = string.Format("{0}{1} {2} {3} -i {4}{5} -threads {6} {7} {8} -sc_threshold 0 {9} -hls_time 10 -start_number 0 -hls_list_size 1440 \"{10}\"",
+            var args = string.Format("{0}{1} {2} {3} -i {4}{5} -map_metadata -1 -threads {6} {7} {8} -sc_threshold 0 {9} -hls_time 10 -start_number 0 -hls_list_size 1440 \"{10}\"",
                 itsOffset,
                 itsOffset,
                 probeSize,
                 probeSize,
                 GetUserAgentParam(state.MediaPath),
                 GetUserAgentParam(state.MediaPath),

+ 1 - 15
MediaBrowser.Api/Playback/Hls/VideoHlsService.cs

@@ -80,21 +80,7 @@ namespace MediaBrowser.Api.Playback.Hls
                     args += " -ab " + bitrate.Value.ToString(UsCulture);
                     args += " -ab " + bitrate.Value.ToString(UsCulture);
                 }
                 }
 
 
-                var volParam = string.Empty;
-                var audioSampleRate = string.Empty;
-
-                // Boost volume to 200% when downsampling from 6ch to 2ch
-                if (channels.HasValue && channels.Value <= 2 && state.AudioStream.Channels.HasValue && state.AudioStream.Channels.Value > 5)
-                {
-                    volParam = ",volume=2.000000";
-                }
-
-                if (state.Request.AudioSampleRate.HasValue)
-                {
-                    audioSampleRate = state.Request.AudioSampleRate.Value + ":";
-                }
-
-                args += string.Format(" -af \"adelay=1,aresample={0}async=1{1}\"", audioSampleRate, volParam);
+                args += " " + GetAudioFilterParam(state, true);
 
 
                 return args;
                 return args;
             }
             }

+ 1 - 1
MediaBrowser.Api/Playback/Progressive/AudioService.cs

@@ -102,7 +102,7 @@ namespace MediaBrowser.Api.Playback.Progressive
 
 
             const string vn = " -vn";
             const string vn = " -vn";
 
 
-            var threads = GetNumberOfThreads();
+            var threads = GetNumberOfThreads(false);
 
 
             return string.Format("{0} -i {1}{2} -threads {3}{4} {5} -id3v2_version 3 -write_id3v1 1 \"{6}\"",
             return string.Format("{0} -i {1}{2} -threads {3}{4} {5} -id3v2_version 3 -write_id3v1 1 \"{6}\"",
                 GetFastSeekCommandLineParameter(request),
                 GetFastSeekCommandLineParameter(request),

+ 19 - 17
MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Common.IO;
+using System;
+using MediaBrowser.Common.IO;
 using MediaBrowser.Common.MediaInfo;
 using MediaBrowser.Common.MediaInfo;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
@@ -106,6 +107,7 @@ namespace MediaBrowser.Api.Playback.Progressive
 
 
             var transferMode = GetHeader("transferMode.dlna.org");
             var transferMode = GetHeader("transferMode.dlna.org");
             responseHeaders["transferMode.dlna.org"] = string.IsNullOrEmpty(transferMode) ? "Streaming" : transferMode;
             responseHeaders["transferMode.dlna.org"] = string.IsNullOrEmpty(transferMode) ? "Streaming" : transferMode;
+            responseHeaders["realTimeInfo.dlna.org"] = "DLNA.ORG_TLAG=*";
 
 
             var contentFeatures = string.Empty;
             var contentFeatures = string.Empty;
             var extension = GetOutputFileExtension(state);
             var extension = GetOutputFileExtension(state);
@@ -118,22 +120,22 @@ namespace MediaBrowser.Api.Playback.Progressive
 
 
             const string dlnaflags = ";DLNA.ORG_FLAGS=01500000000000000000000000000000";
             const string dlnaflags = ";DLNA.ORG_FLAGS=01500000000000000000000000000000";
 
 
-            //if (string.Equals(extension, ".mp3", StringComparison.OrdinalIgnoreCase))
-            //{
-            //    contentFeatures = "DLNA.ORG_PN=MP3";
-            //}
-            //else if (string.Equals(extension, ".aac", StringComparison.OrdinalIgnoreCase))
-            //{
-            //    contentFeatures = "DLNA.ORG_PN=AAC_ISO";
-            //}
-            //else if (string.Equals(extension, ".wma", StringComparison.OrdinalIgnoreCase))
-            //{
-            //    contentFeatures = "DLNA.ORG_PN=WMABASE";
-            //}
-            //else if (string.Equals(extension, ".avi", StringComparison.OrdinalIgnoreCase))
-            //{
-            //    contentFeatures = "DLNA.ORG_PN=AVI";
-            //}
+            if (string.Equals(extension, ".mp3", StringComparison.OrdinalIgnoreCase))
+            {
+                contentFeatures = "DLNA.ORG_PN=MP3";
+            }
+            else if (string.Equals(extension, ".aac", StringComparison.OrdinalIgnoreCase))
+            {
+                contentFeatures = "DLNA.ORG_PN=AAC_ISO";
+            }
+            else if (string.Equals(extension, ".wma", StringComparison.OrdinalIgnoreCase))
+            {
+                contentFeatures = "DLNA.ORG_PN=WMABASE";
+            }
+            else if (string.Equals(extension, ".avi", StringComparison.OrdinalIgnoreCase))
+            {
+                contentFeatures = "DLNA.ORG_PN=AVI";
+            }
             //else if (string.Equals(extension, ".mp4", StringComparison.OrdinalIgnoreCase))
             //else if (string.Equals(extension, ".mp4", StringComparison.OrdinalIgnoreCase))
             //{
             //{
             //    contentFeatures = "DLNA.ORG_PN=MPEG4_P2_SP_AAC";
             //    contentFeatures = "DLNA.ORG_PN=MPEG4_P2_SP_AAC";

+ 4 - 17
MediaBrowser.Api/Playback/Progressive/VideoService.cs

@@ -104,9 +104,9 @@ namespace MediaBrowser.Api.Playback.Progressive
                 format = " -f mp4 -movflags frag_keyframe+empty_moov";
                 format = " -f mp4 -movflags frag_keyframe+empty_moov";
             }
             }
 
 
-            var threads = string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase) ? 2 : GetNumberOfThreads();
+            var threads = GetNumberOfThreads(string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase));
 
 
-            return string.Format("{0} {1} {2} -i {3}{4}{5} {6} {7} -threads {8} {9}{10} \"{11}\"",
+            return string.Format("{0} {1} {2} -i {3}{4}{5} {6} {7} -map_metadata -1 -threads {8} {9}{10} \"{11}\"",
                 probeSize,
                 probeSize,
                 GetUserAgentParam(state.MediaPath),
                 GetUserAgentParam(state.MediaPath),
                 GetFastSeekCommandLineParameter(state.Request),
                 GetFastSeekCommandLineParameter(state.Request),
@@ -170,6 +170,7 @@ namespace MediaBrowser.Api.Playback.Progressive
             if (bitrate.HasValue)
             if (bitrate.HasValue)
             {
             {
                 qualityParam += string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture));
                 qualityParam += string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture));
+                //qualityParam += string.Format(" -maxrate {0} -bufsize {1}", bitrate.Value.ToString(UsCulture), (bitrate.Value * 2).ToString(UsCulture));
             }
             }
 
 
             if (!string.IsNullOrEmpty(qualityParam))
             if (!string.IsNullOrEmpty(qualityParam))
@@ -238,21 +239,7 @@ namespace MediaBrowser.Api.Playback.Progressive
                 args += " -ab " + bitrate.Value.ToString(UsCulture);
                 args += " -ab " + bitrate.Value.ToString(UsCulture);
             }
             }
 
 
-            var volParam = string.Empty;
-            var AudioSampleRate = string.Empty;
-
-            // Boost volume to 200% when downsampling from 6ch to 2ch
-            if (channels.HasValue && channels.Value <= 2 && state.AudioStream.Channels.HasValue && state.AudioStream.Channels.Value > 5)
-            {
-                volParam = ",volume=2.000000";
-            }
-
-            if (state.Request.AudioSampleRate.HasValue)
-            {
-                AudioSampleRate = state.Request.AudioSampleRate.Value + ":";
-            }
-
-            args += string.Format(" -af \"aresample={0}async=1{1}\"", AudioSampleRate, volParam);
+            args += " " + GetAudioFilterParam(state, true);
 
 
             return args;
             return args;
         }
         }

+ 2 - 4
MediaBrowser.Common/MediaInfo/IMediaEncoder.cs

@@ -40,11 +40,10 @@ namespace MediaBrowser.Common.MediaInfo
         /// <param name="inputFiles">The input files.</param>
         /// <param name="inputFiles">The input files.</param>
         /// <param name="type">The type.</param>
         /// <param name="type">The type.</param>
         /// <param name="subtitleStreamIndex">Index of the subtitle stream.</param>
         /// <param name="subtitleStreamIndex">Index of the subtitle stream.</param>
-        /// <param name="offset">The offset.</param>
         /// <param name="outputPath">The output path.</param>
         /// <param name="outputPath">The output path.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
-        Task ExtractTextSubtitle(string[] inputFiles, InputType type, int subtitleStreamIndex, TimeSpan offset, string outputPath, CancellationToken cancellationToken);
+        Task ExtractTextSubtitle(string[] inputFiles, InputType type, int subtitleStreamIndex, string outputPath, CancellationToken cancellationToken);
 
 
         /// <summary>
         /// <summary>
         /// Converts the text subtitle to ass.
         /// Converts the text subtitle to ass.
@@ -52,10 +51,9 @@ namespace MediaBrowser.Common.MediaInfo
         /// <param name="inputPath">The input path.</param>
         /// <param name="inputPath">The input path.</param>
         /// <param name="outputPath">The output path.</param>
         /// <param name="outputPath">The output path.</param>
         /// <param name="language">The language.</param>
         /// <param name="language">The language.</param>
-        /// <param name="offset">The offset.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
-        Task ConvertTextSubtitleToAss(string inputPath, string outputPath, string language, TimeSpan offset, CancellationToken cancellationToken);
+        Task ConvertTextSubtitleToAss(string inputPath, string outputPath, string language, CancellationToken cancellationToken);
 
 
         /// <summary>
         /// <summary>
         /// Gets the media info.
         /// Gets the media info.

+ 1 - 1
MediaBrowser.Common/Net/MimeTypes.cs

@@ -67,7 +67,7 @@ namespace MediaBrowser.Common.Net
             }
             }
             if (ext.Equals(".avi", StringComparison.OrdinalIgnoreCase))
             if (ext.Equals(".avi", StringComparison.OrdinalIgnoreCase))
             {
             {
-                return "video/avi";
+                return "video/x-msvideo";
             }
             }
             if (ext.Equals(".m4v", StringComparison.OrdinalIgnoreCase))
             if (ext.Equals(".m4v", StringComparison.OrdinalIgnoreCase))
             {
             {

+ 2 - 3
MediaBrowser.Controller/MediaInfo/FFMpegManager.cs

@@ -235,12 +235,11 @@ namespace MediaBrowser.Controller.MediaInfo
         /// </summary>
         /// </summary>
         /// <param name="mediaPath">The media path.</param>
         /// <param name="mediaPath">The media path.</param>
         /// <param name="subtitleStream">The subtitle stream.</param>
         /// <param name="subtitleStream">The subtitle stream.</param>
-        /// <param name="offset">The offset.</param>
         /// <param name="outputExtension">The output extension.</param>
         /// <param name="outputExtension">The output extension.</param>
         /// <returns>System.String.</returns>
         /// <returns>System.String.</returns>
-        public string GetSubtitleCachePath(string mediaPath, MediaStream subtitleStream, TimeSpan? offset, string outputExtension)
+        public string GetSubtitleCachePath(string mediaPath, MediaStream subtitleStream, string outputExtension)
         {
         {
-            var ticksParam = offset.HasValue ? "_" + offset.Value.Ticks : "";
+            var ticksParam = string.Empty;
 
 
             if (subtitleStream.IsExternal)
             if (subtitleStream.IsExternal)
             {
             {

+ 12 - 0
MediaBrowser.Model/LiveTv/ChannelQuery.cs

@@ -17,5 +17,17 @@ namespace MediaBrowser.Model.LiveTv
         /// </summary>
         /// </summary>
         /// <value>The user identifier.</value>
         /// <value>The user identifier.</value>
         public string UserId { get; set; }
         public string UserId { get; set; }
+
+        /// <summary>
+        /// Skips over a given number of items within the results. Use for paging.
+        /// </summary>
+        /// <value>The start index.</value>
+        public int? StartIndex { get; set; }
+
+        /// <summary>
+        /// The maximum number of items to return
+        /// </summary>
+        /// <value>The limit.</value>
+        public int? Limit { get; set; }
     }
     }
 }
 }

+ 11 - 9
MediaBrowser.Providers/Movies/ManualMovieDbImageProvider.cs

@@ -51,7 +51,7 @@ namespace MediaBrowser.Providers.Movies
         {
         {
             var list = new List<RemoteImageInfo>();
             var list = new List<RemoteImageInfo>();
 
 
-            var results = FetchImages(item, _jsonSerializer);
+            var results = await FetchImages((BaseItem)item, _jsonSerializer, cancellationToken).ConfigureAwait(false);
 
 
             if (results == null)
             if (results == null)
             {
             {
@@ -62,7 +62,7 @@ namespace MediaBrowser.Providers.Movies
 
 
             var tmdbImageUrl = tmdbSettings.images.base_url + "original";
             var tmdbImageUrl = tmdbSettings.images.base_url + "original";
 
 
-            list.AddRange(GetPosters(results, item).Select(i => new RemoteImageInfo
+            list.AddRange(GetPosters(results).Select(i => new RemoteImageInfo
             {
             {
                 Url = tmdbImageUrl + i.file_path,
                 Url = tmdbImageUrl + i.file_path,
                 CommunityRating = i.vote_average,
                 CommunityRating = i.vote_average,
@@ -75,7 +75,7 @@ namespace MediaBrowser.Providers.Movies
                 RatingType = RatingType.Score
                 RatingType = RatingType.Score
             }));
             }));
 
 
-            list.AddRange(GetBackdrops(results, item).Select(i => new RemoteImageInfo
+            list.AddRange(GetBackdrops(results).Select(i => new RemoteImageInfo
             {
             {
                 Url = tmdbImageUrl + i.file_path,
                 Url = tmdbImageUrl + i.file_path,
                 CommunityRating = i.vote_average,
                 CommunityRating = i.vote_average,
@@ -119,9 +119,8 @@ namespace MediaBrowser.Providers.Movies
         /// Gets the posters.
         /// Gets the posters.
         /// </summary>
         /// </summary>
         /// <param name="images">The images.</param>
         /// <param name="images">The images.</param>
-        /// <param name="item">The item.</param>
         /// <returns>IEnumerable{MovieDbProvider.Poster}.</returns>
         /// <returns>IEnumerable{MovieDbProvider.Poster}.</returns>
-        private IEnumerable<MovieDbProvider.Poster> GetPosters(MovieDbProvider.Images images, IHasImages item)
+        private IEnumerable<MovieDbProvider.Poster> GetPosters(MovieDbProvider.Images images)
         {
         {
             return images.posters ?? new List<MovieDbProvider.Poster>();
             return images.posters ?? new List<MovieDbProvider.Poster>();
         }
         }
@@ -130,9 +129,8 @@ namespace MediaBrowser.Providers.Movies
         /// Gets the backdrops.
         /// Gets the backdrops.
         /// </summary>
         /// </summary>
         /// <param name="images">The images.</param>
         /// <param name="images">The images.</param>
-        /// <param name="item">The item.</param>
         /// <returns>IEnumerable{MovieDbProvider.Backdrop}.</returns>
         /// <returns>IEnumerable{MovieDbProvider.Backdrop}.</returns>
-        private IEnumerable<MovieDbProvider.Backdrop> GetBackdrops(MovieDbProvider.Images images, IHasImages item)
+        private IEnumerable<MovieDbProvider.Backdrop> GetBackdrops(MovieDbProvider.Images images)
         {
         {
             var eligibleBackdrops = images.backdrops == null ? new List<MovieDbProvider.Backdrop>() :
             var eligibleBackdrops = images.backdrops == null ? new List<MovieDbProvider.Backdrop>() :
                 images.backdrops
                 images.backdrops
@@ -147,10 +145,14 @@ namespace MediaBrowser.Providers.Movies
         /// </summary>
         /// </summary>
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <param name="jsonSerializer">The json serializer.</param>
         /// <param name="jsonSerializer">The json serializer.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task{MovieImages}.</returns>
         /// <returns>Task{MovieImages}.</returns>
-        private MovieDbProvider.Images FetchImages(IHasImages item, IJsonSerializer jsonSerializer)
+        private async Task<MovieDbProvider.Images> FetchImages(BaseItem item, IJsonSerializer jsonSerializer,
+            CancellationToken cancellationToken)
         {
         {
-            var path = MovieDbProvider.Current.GetDataFilePath((BaseItem)item);
+            await MovieDbProvider.Current.EnsureMovieInfo(item, cancellationToken).ConfigureAwait(false);
+
+            var path = MovieDbProvider.Current.GetDataFilePath(item);
 
 
             if (!string.IsNullOrEmpty(path))
             if (!string.IsNullOrEmpty(path))
             {
             {

+ 1 - 1
MediaBrowser.Providers/Movies/ManualMovieDbPersonImageProvider.cs

@@ -52,7 +52,7 @@ namespace MediaBrowser.Providers.Movies
 
 
             if (!string.IsNullOrEmpty(id))
             if (!string.IsNullOrEmpty(id))
             {
             {
-                await MovieDbPersonProvider.Current.DownloadPersonInfoIfNeeded(id, cancellationToken).ConfigureAwait(false);
+                await MovieDbPersonProvider.Current.EnsurePersonInfo(id, cancellationToken).ConfigureAwait(false);
 
 
                 var dataFilePath = MovieDbPersonProvider.GetPersonDataFilePath(_config.ApplicationPaths, id);
                 var dataFilePath = MovieDbPersonProvider.GetPersonDataFilePath(_config.ApplicationPaths, id);
 
 

+ 2 - 2
MediaBrowser.Providers/Movies/MovieDbPersonProvider.cs

@@ -235,7 +235,7 @@ namespace MediaBrowser.Providers.Movies
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
         private async Task FetchInfo(Person person, string id, bool isForcedRefresh, CancellationToken cancellationToken)
         private async Task FetchInfo(Person person, string id, bool isForcedRefresh, CancellationToken cancellationToken)
         {
         {
-            await DownloadPersonInfoIfNeeded(id, cancellationToken).ConfigureAwait(false);
+            await EnsurePersonInfo(id, cancellationToken).ConfigureAwait(false);
 
 
             if (isForcedRefresh || !HasAltMeta(person))
             if (isForcedRefresh || !HasAltMeta(person))
             {
             {
@@ -249,7 +249,7 @@ namespace MediaBrowser.Providers.Movies
             }
             }
         }
         }
 
 
-        internal async Task DownloadPersonInfoIfNeeded(string id, CancellationToken cancellationToken)
+        internal async Task EnsurePersonInfo(string id, CancellationToken cancellationToken)
         {
         {
             var personDataPath = GetPersonDataPath(ConfigurationManager.ApplicationPaths, id);
             var personDataPath = GetPersonDataPath(ConfigurationManager.ApplicationPaths, id);
 
 

+ 26 - 1
MediaBrowser.Providers/Movies/MovieDbProvider.cs

@@ -558,6 +558,31 @@ namespace MediaBrowser.Providers.Movies
             JsonSerializer.SerializeToFile(mainResult, dataFilePath);
             JsonSerializer.SerializeToFile(mainResult, dataFilePath);
         }
         }
 
 
+        internal Task EnsureMovieInfo(BaseItem item, CancellationToken cancellationToken)
+        {
+            var path = GetDataFilePath(item);
+
+            var fileInfo = _fileSystem.GetFileSystemInfo(path);
+
+            if (fileInfo.Exists)
+            {
+                // If it's recent or automatic updates are enabled, don't re-download
+                if (ConfigurationManager.Configuration.EnableTmdbUpdates || (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 7)
+                {
+                    return Task.FromResult(true);
+                }
+            }
+
+            var id = item.GetProviderId(MetadataProviders.Tmdb);
+
+            if (string.IsNullOrEmpty(id))
+            {
+                return Task.FromResult(true);
+            }
+
+            return DownloadMovieInfo(id, item is BoxSet, item.GetPreferredMetadataLanguage(), cancellationToken);
+        }
+
         /// <summary>
         /// <summary>
         /// Gets the data file path.
         /// Gets the data file path.
         /// </summary>
         /// </summary>
@@ -575,7 +600,7 @@ namespace MediaBrowser.Providers.Movies
             return GetDataFilePath(item is BoxSet, id, item.GetPreferredMetadataLanguage());
             return GetDataFilePath(item is BoxSet, id, item.GetPreferredMetadataLanguage());
         }
         }
 
 
-        internal string GetDataFilePath(bool isBoxset, string tmdbId, string preferredLanguage)
+        private string GetDataFilePath(bool isBoxset, string tmdbId, string preferredLanguage)
         {
         {
             var path = GetMovieDataPath(ConfigurationManager.ApplicationPaths, isBoxset, tmdbId);
             var path = GetMovieDataPath(ConfigurationManager.ApplicationPaths, isBoxset, tmdbId);
 
 

+ 1 - 1
MediaBrowser.Providers/Movies/MovieUpdatesPrescanTask.cs

@@ -69,7 +69,7 @@ namespace MediaBrowser.Providers.Movies
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
         public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
         public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
         {
         {
-            if (!_config.Configuration.EnableInternetProviders)
+            if (!_config.Configuration.EnableInternetProviders && !_config.Configuration.EnableTmdbUpdates)
             {
             {
                 progress.Report(100);
                 progress.Report(100);
                 return;
                 return;

+ 22 - 7
MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs

@@ -98,7 +98,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                     });
                     });
             }
             }
 
 
-            var returnChannels = channels.OrderBy(i =>
+            channels = channels.OrderBy(i =>
             {
             {
                 double number = 0;
                 double number = 0;
 
 
@@ -109,14 +109,29 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
 
                 return number;
                 return number;
 
 
-            }).ThenBy(i => i.Name)
-            .Select(i => _tvDtoService.GetChannelInfoDto(i, GetCurrentProgram(i.ChannelInfo.Id), user))
-            .ToArray();
+            }).ThenBy(i => i.Name);
+
+            var allChannels = channels.ToList();
+            IEnumerable<LiveTvChannel> allEnumerable = allChannels;
+
+            if (query.StartIndex.HasValue)
+            {
+                allEnumerable = allEnumerable.Skip(query.StartIndex.Value);
+            }
+
+            if (query.Limit.HasValue)
+            {
+                allEnumerable = allEnumerable.Take(query.Limit.Value);
+            }
+
+            var returnChannels = allEnumerable
+                .Select(i => _tvDtoService.GetChannelInfoDto(i, GetCurrentProgram(i.ChannelInfo.Id), user))
+                .ToArray();
 
 
             var result = new QueryResult<ChannelInfoDto>
             var result = new QueryResult<ChannelInfoDto>
             {
             {
                 Items = returnChannels,
                 Items = returnChannels,
-                TotalRecordCount = returnChannels.Length
+                TotalRecordCount = allChannels.Count
             };
             };
 
 
             return Task.FromResult(result);
             return Task.FromResult(result);
@@ -575,9 +590,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                     .Where(i => _tvDtoService.GetInternalSeriesTimerId(currentServiceName, i.SeriesTimerId) == guid);
                     .Where(i => _tvDtoService.GetInternalSeriesTimerId(currentServiceName, i.SeriesTimerId) == guid);
             }
             }
 
 
-            IEnumerable<ILiveTvRecording> entities = await GetEntities(recordings, service.Name, cancellationToken).ConfigureAwait(false);
+            recordings = recordings.OrderByDescending(i => i.StartDate);
 
 
-            entities = entities.OrderByDescending(i => i.RecordingInfo.StartDate);
+            IEnumerable<ILiveTvRecording> entities = await GetEntities(recordings, service.Name, cancellationToken).ConfigureAwait(false);
 
 
             if (user != null)
             if (user != null)
             {
             {

+ 15 - 25
MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs

@@ -96,7 +96,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
         {
         {
             return _semaphoreLocks.GetOrAdd(filename, key => new SemaphoreSlim(1, 1));
             return _semaphoreLocks.GetOrAdd(filename, key => new SemaphoreSlim(1, 1));
         }
         }
-        
+
         /// <summary>
         /// <summary>
         /// Gets the media info.
         /// Gets the media info.
         /// </summary>
         /// </summary>
@@ -378,11 +378,9 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
         /// <param name="inputPath">The input path.</param>
         /// <param name="inputPath">The input path.</param>
         /// <param name="outputPath">The output path.</param>
         /// <param name="outputPath">The output path.</param>
         /// <param name="language">The language.</param>
         /// <param name="language">The language.</param>
-        /// <param name="offset">The offset.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
-        public async Task ConvertTextSubtitleToAss(string inputPath, string outputPath, string language, TimeSpan offset,
-                                                   CancellationToken cancellationToken)
+        public async Task ConvertTextSubtitleToAss(string inputPath, string outputPath, string language, CancellationToken cancellationToken)
         {
         {
             var semaphore = GetLock(outputPath);
             var semaphore = GetLock(outputPath);
 
 
@@ -392,7 +390,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
             {
             {
                 if (!File.Exists(outputPath))
                 if (!File.Exists(outputPath))
                 {
                 {
-                    await ConvertTextSubtitleToAssInternal(inputPath, outputPath, language, offset).ConfigureAwait(false);
+                    await ConvertTextSubtitleToAssInternal(inputPath, outputPath, language).ConfigureAwait(false);
                 }
                 }
             }
             }
             finally
             finally
@@ -409,13 +407,12 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
         /// <param name="inputPath">The input path.</param>
         /// <param name="inputPath">The input path.</param>
         /// <param name="outputPath">The output path.</param>
         /// <param name="outputPath">The output path.</param>
         /// <param name="language">The language.</param>
         /// <param name="language">The language.</param>
-        /// <param name="offset">The offset.</param>
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
         /// <exception cref="System.ArgumentNullException">inputPath
         /// <exception cref="System.ArgumentNullException">inputPath
         /// or
         /// or
         /// outputPath</exception>
         /// outputPath</exception>
         /// <exception cref="System.ApplicationException"></exception>
         /// <exception cref="System.ApplicationException"></exception>
-        private async Task ConvertTextSubtitleToAssInternal(string inputPath, string outputPath, string language, TimeSpan offset)
+        private async Task ConvertTextSubtitleToAssInternal(string inputPath, string outputPath, string language)
         {
         {
             if (string.IsNullOrEmpty(inputPath))
             if (string.IsNullOrEmpty(inputPath))
             {
             {
@@ -427,8 +424,6 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
                 throw new ArgumentNullException("outputPath");
                 throw new ArgumentNullException("outputPath");
             }
             }
 
 
-            
-            var slowSeekParam = offset.TotalSeconds > 0 ? " -ss " + offset.TotalSeconds.ToString(UsCulture) : string.Empty;
 
 
             var encodingParam = string.IsNullOrEmpty(language) ? string.Empty :
             var encodingParam = string.IsNullOrEmpty(language) ? string.Empty :
                 GetSubtitleLanguageEncodingParam(language) + " ";
                 GetSubtitleLanguageEncodingParam(language) + " ";
@@ -444,7 +439,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
                             UseShellExecute = false,
                             UseShellExecute = false,
                             FileName = FFMpegPath,
                             FileName = FFMpegPath,
                             Arguments =
                             Arguments =
-                                string.Format("{0} -i \"{1}\" {2} -c:s ass \"{3}\"", encodingParam,  inputPath,  slowSeekParam, outputPath),
+                                string.Format("{0} -i \"{1}\" -c:s ass \"{2}\"", encodingParam, inputPath, outputPath),
 
 
                             WindowStyle = ProcessWindowStyle.Hidden,
                             WindowStyle = ProcessWindowStyle.Hidden,
                             ErrorDialog = false
                             ErrorDialog = false
@@ -557,7 +552,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
 
 
             return string.Empty;
             return string.Empty;
         }
         }
-        
+
         /// <summary>
         /// <summary>
         /// Gets the subtitle language encoding param.
         /// Gets the subtitle language encoding param.
         /// </summary>
         /// </summary>
@@ -598,7 +593,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
                 case "vie":
                 case "vie":
                     return "-sub_charenc windows-1258";
                     return "-sub_charenc windows-1258";
                 case "kor":
                 case "kor":
-                		return "-sub_charenc cp949";
+                    return "-sub_charenc cp949";
                 default:
                 default:
                     return "-sub_charenc windows-1252";
                     return "-sub_charenc windows-1252";
             }
             }
@@ -610,12 +605,11 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
         /// <param name="inputFiles">The input files.</param>
         /// <param name="inputFiles">The input files.</param>
         /// <param name="type">The type.</param>
         /// <param name="type">The type.</param>
         /// <param name="subtitleStreamIndex">Index of the subtitle stream.</param>
         /// <param name="subtitleStreamIndex">Index of the subtitle stream.</param>
-        /// <param name="offset">The offset.</param>
         /// <param name="outputPath">The output path.</param>
         /// <param name="outputPath">The output path.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
         /// <exception cref="System.ArgumentException">Must use inputPath list overload</exception>
         /// <exception cref="System.ArgumentException">Must use inputPath list overload</exception>
-        public async Task ExtractTextSubtitle(string[] inputFiles, InputType type, int subtitleStreamIndex, TimeSpan offset, string outputPath, CancellationToken cancellationToken)
+        public async Task ExtractTextSubtitle(string[] inputFiles, InputType type, int subtitleStreamIndex, string outputPath, CancellationToken cancellationToken)
         {
         {
             var semaphore = GetLock(outputPath);
             var semaphore = GetLock(outputPath);
 
 
@@ -625,7 +619,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
             {
             {
                 if (!File.Exists(outputPath))
                 if (!File.Exists(outputPath))
                 {
                 {
-                    await ExtractTextSubtitleInternal(GetInputArgument(inputFiles, type), subtitleStreamIndex, offset, outputPath, cancellationToken).ConfigureAwait(false);
+                    await ExtractTextSubtitleInternal(GetInputArgument(inputFiles, type), subtitleStreamIndex, outputPath, cancellationToken).ConfigureAwait(false);
                 }
                 }
             }
             }
             finally
             finally
@@ -639,7 +633,6 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
         /// </summary>
         /// </summary>
         /// <param name="inputPath">The input path.</param>
         /// <param name="inputPath">The input path.</param>
         /// <param name="subtitleStreamIndex">Index of the subtitle stream.</param>
         /// <param name="subtitleStreamIndex">Index of the subtitle stream.</param>
-        /// <param name="offset">The offset.</param>
         /// <param name="outputPath">The output path.</param>
         /// <param name="outputPath">The output path.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
@@ -649,7 +642,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
         /// or
         /// or
         /// cancellationToken</exception>
         /// cancellationToken</exception>
         /// <exception cref="System.ApplicationException"></exception>
         /// <exception cref="System.ApplicationException"></exception>
-        private async Task ExtractTextSubtitleInternal(string inputPath, int subtitleStreamIndex, TimeSpan offset, string outputPath, CancellationToken cancellationToken)
+        private async Task ExtractTextSubtitleInternal(string inputPath, int subtitleStreamIndex, string outputPath, CancellationToken cancellationToken)
         {
         {
             if (string.IsNullOrEmpty(inputPath))
             if (string.IsNullOrEmpty(inputPath))
             {
             {
@@ -661,9 +654,6 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
                 throw new ArgumentNullException("outputPath");
                 throw new ArgumentNullException("outputPath");
             }
             }
 
 
-                        
-            var slowSeekParam = GetSlowSeekCommandLineParameter(offset);
-            var fastSeekParam = GetFastSeekCommandLineParameter(offset);
 
 
             var process = new Process
             var process = new Process
             {
             {
@@ -676,7 +666,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
                     RedirectStandardError = true,
                     RedirectStandardError = true,
 
 
                     FileName = FFMpegPath,
                     FileName = FFMpegPath,
-                    Arguments = string.Format(" {0} -i {1} {2} -map 0:{3} -an -vn -c:s ass \"{4}\"", fastSeekParam, inputPath, slowSeekParam, subtitleStreamIndex, outputPath),
+                    Arguments = string.Format("-i {0} -map 0:{1} -an -vn -c:s ass \"{2}\"", inputPath, subtitleStreamIndex, outputPath),
                     WindowStyle = ProcessWindowStyle.Hidden,
                     WindowStyle = ProcessWindowStyle.Hidden,
                     ErrorDialog = false
                     ErrorDialog = false
                 }
                 }
@@ -872,8 +862,8 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
                 switch (threedFormat.Value)
                 switch (threedFormat.Value)
                 {
                 {
                     case Video3DFormat.HalfSideBySide:
                     case Video3DFormat.HalfSideBySide:
-                    vf = "crop=iw/2:ih:0:0,scale=(iw*2):ih,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,scale=600:600/dar";
-                    // hsbs crop width in half,scale to correct size, set the display aspect,crop out any black bars we may have made the scale width to 600. Work out the correct height based on the display aspect it will maintain the aspect where -1 in this case (3d) may not.
+                        vf = "crop=iw/2:ih:0:0,scale=(iw*2):ih,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,scale=600:600/dar";
+                        // hsbs crop width in half,scale to correct size, set the display aspect,crop out any black bars we may have made the scale width to 600. Work out the correct height based on the display aspect it will maintain the aspect where -1 in this case (3d) may not.
                         break;
                         break;
                     case Video3DFormat.FullSideBySide:
                     case Video3DFormat.FullSideBySide:
                         vf = "crop=iw/2:ih:0:0,setdar=dar=a,,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,scale=600:600/dar";
                         vf = "crop=iw/2:ih:0:0,setdar=dar=a,,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,scale=600:600/dar";
@@ -882,7 +872,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
                     case Video3DFormat.HalfTopAndBottom:
                     case Video3DFormat.HalfTopAndBottom:
                         vf = "crop=iw:ih/2:0:0,scale=(iw*2):ih),setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,scale=600:600/dar";
                         vf = "crop=iw:ih/2:0:0,scale=(iw*2):ih),setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,scale=600:600/dar";
                         //htab crop heigh in half,scale to correct size, set the display aspect,crop out any black bars we may have made the scale width to 600
                         //htab crop heigh in half,scale to correct size, set the display aspect,crop out any black bars we may have made the scale width to 600
-                        break; 
+                        break;
                     case Video3DFormat.FullTopAndBottom:
                     case Video3DFormat.FullTopAndBottom:
                         vf = "crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,scale=600:600/dar";
                         vf = "crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,scale=600:600/dar";
                         // ftab crop heigt in half, set the display aspect,crop out any black bars we may have made the scale width to 600
                         // ftab crop heigt in half, set the display aspect,crop out any black bars we may have made the scale width to 600
@@ -892,7 +882,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
 
 
             // Use ffmpeg to sample 100 (we can drop this if required using thumbnail=50 for 50 frames) frames and pick the best thumbnail. Have a fall back just in case.
             // Use ffmpeg to sample 100 (we can drop this if required using thumbnail=50 for 50 frames) frames and pick the best thumbnail. Have a fall back just in case.
             var args = useIFrame ? string.Format("-i {0} -threads 0 -v quiet -vframes 1 -vf \"thumbnail,{2}\" -f image2 \"{1}\"", inputPath, outputPath, vf) :
             var args = useIFrame ? string.Format("-i {0} -threads 0 -v quiet -vframes 1 -vf \"thumbnail,{2}\" -f image2 \"{1}\"", inputPath, outputPath, vf) :
-                string.Format("-i {0} -threads 0 -v quiet -vframes 1 -vf \"{2}\" -f image2 \"{1}\"", inputPath, outputPath, vf); 
+                string.Format("-i {0} -threads 0 -v quiet -vframes 1 -vf \"{2}\" -f image2 \"{1}\"", inputPath, outputPath, vf);
 
 
             var probeSize = GetProbeSizeArgument(type);
             var probeSize = GetProbeSizeArgument(type);
 
 

+ 6 - 0
MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs

@@ -90,6 +90,12 @@ namespace MediaBrowser.Server.Implementations.Session
 
 
             var vals = message.Data.Split('|');
             var vals = message.Data.Split('|');
 
 
+            if (vals.Length < 3)
+            {
+                _logger.Error("Client sent invalid identity message.");
+                return;
+            }
+
             var client = vals[0];
             var client = vals[0];
             var deviceId = vals[1];
             var deviceId = vals[1];
             var version = vals[2];
             var version = vals[2];

+ 19 - 6
MediaBrowser.WebDashboard/ApiClient.js

@@ -436,13 +436,26 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi
 
 
         self.getLiveTvPrograms = function (options) {
         self.getLiveTvPrograms = function (options) {
 
 
-            var url = self.getUrl("LiveTv/Programs", options || {});
+            options = options || {};
+            
+            if (options.channelIds) {
 
 
-            return self.ajax({
-                type: "GET",
-                url: url,
-                dataType: "json"
-            });
+                return self.ajax({
+                    type: "POST",
+                    url: self.getUrl("LiveTv/Programs"),
+                    data: JSON.stringify(options),
+                    contentType: "application/json",
+                    dataType: "json"
+                });
+
+            } else {
+                
+                return self.ajax({
+                    type: "GET",
+                    url: self.getUrl("LiveTv/Programs", options),
+                    dataType: "json"
+                });
+            }
         };
         };
 
 
         self.getLiveTvRecordings = function (options) {
         self.getLiveTvRecordings = function (options) {

+ 1 - 1
MediaBrowser.WebDashboard/packages.config

@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
 <packages>
-  <package id="MediaBrowser.ApiClient.Javascript" version="3.0.219" targetFramework="net45" />
+  <package id="MediaBrowser.ApiClient.Javascript" version="3.0.223" targetFramework="net45" />
 </packages>
 </packages>