瀏覽代碼

add movie metadata support to live tv

Luke Pulverenti 10 年之前
父節點
當前提交
b1d2841583

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

@@ -182,6 +182,24 @@ namespace MediaBrowser.Api.LiveTv
 
         [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; }
+
+        [ApiMember(Name = "IsMovie", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
+        public bool? IsMovie { get; set; }
+
+        [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; }
+
+        [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; }
+
+        [ApiMember(Name = "SortBy", Description = "Optional. Specify one or more sort orders, comma delimeted. Options: Name, StartDate", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        public string SortBy { get; set; }
+
+        [ApiMember(Name = "SortOrder", Description = "Sort Order - Ascending,Descending", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public SortOrder? SortOrder { get; set; }
+
+        [ApiMember(Name = "Genres", Description = "The genres to return guide information for.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")]
+        public string Genres { get; set; }
     }
 
     [Route("/LiveTv/Programs/Recommended", "GET", Summary = "Gets available live tv epgs..")]
@@ -199,6 +217,9 @@ namespace MediaBrowser.Api.LiveTv
 
         [ApiMember(Name = "HasAired", Description = "Optional. Filter by programs that have completed airing, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
         public bool? HasAired { get; set; }
+
+        [ApiMember(Name = "IsMovie", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
+        public bool? IsMovie { get; set; }
     }
 
     [Route("/LiveTv/Programs/{Id}", "GET", Summary = "Gets a live tv program")]
@@ -371,7 +392,7 @@ namespace MediaBrowser.Api.LiveTv
         {
             var query = new ProgramQuery
             {
-                ChannelIdList = (request.ChannelIds ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToArray(),
+                ChannelIds = (request.ChannelIds ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToArray(),
                 UserId = request.UserId,
                 HasAired = request.HasAired
             };
@@ -396,6 +417,13 @@ namespace MediaBrowser.Api.LiveTv
                 query.MaxEndDate = DateTime.Parse(request.MaxEndDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
             }
 
+            query.StartIndex = request.StartIndex;
+            query.Limit = request.Limit;
+            query.SortBy = (request.SortBy ?? String.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
+            query.SortOrder = request.SortOrder;
+            query.IsMovie = request.IsMovie;
+            query.Genres = (request.Genres ?? String.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
+
             var result = await _liveTvManager.GetPrograms(query, CancellationToken.None).ConfigureAwait(false);
 
             return ToOptimizedSerializedResultUsingCache(result);
@@ -408,7 +436,8 @@ namespace MediaBrowser.Api.LiveTv
                 UserId = request.UserId,
                 IsAiring = request.IsAiring,
                 Limit = request.Limit,
-                HasAired = request.HasAired
+                HasAired = request.HasAired,
+                IsMovie = request.IsMovie
             };
 
             var result = await _liveTvManager.GetRecommendedPrograms(query, CancellationToken.None).ConfigureAwait(false);

+ 5 - 0
MediaBrowser.Controller/Entities/BaseItem.cs

@@ -239,6 +239,11 @@ namespace MediaBrowser.Controller.Entities
             get { return this.GetImagePath(ImageType.Primary); }
         }
 
+        public virtual bool IsInternetMetadataEnabled()
+        {
+            return ConfigurationManager.Configuration.EnableInternetProviders;
+        }
+
         public virtual bool CanDelete()
         {
             var locationType = LocationType;

+ 6 - 0
MediaBrowser.Controller/Entities/IHasImages.cs

@@ -184,6 +184,12 @@ namespace MediaBrowser.Controller.Entities
         /// </summary>
         /// <value><c>true</c> if [always scan internal metadata path]; otherwise, <c>false</c>.</value>
         bool AlwaysScanInternalMetadataPath { get; }
+
+        /// <summary>
+        /// Determines whether [is internet metadata enabled].
+        /// </summary>
+        /// <returns><c>true</c> if [is internet metadata enabled]; otherwise, <c>false</c>.</returns>
+        bool IsInternetMetadataEnabled();
     }
 
     public static class HasImagesExtensions

+ 20 - 1
MediaBrowser.Controller/LiveTv/LiveTvProgram.cs

@@ -1,5 +1,6 @@
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.LiveTv;
 using MediaBrowser.Model.Users;
@@ -11,7 +12,7 @@ using System.Threading.Tasks;
 
 namespace MediaBrowser.Controller.LiveTv
 {
-    public class LiveTvProgram : BaseItem, ILiveTvItem
+    public class LiveTvProgram : BaseItem, ILiveTvItem, IHasLookupInfo<LiveTvProgramLookupInfo>
     {
         /// <summary>
         /// Gets the user data key.
@@ -220,5 +221,23 @@ namespace MediaBrowser.Controller.LiveTv
         {
             return false;
         }
+
+        public override bool IsInternetMetadataEnabled()
+        {
+            if (IsMovie)
+            {
+                var options = (LiveTvOptions)ConfigurationManager.GetConfiguration("livetv");
+                return options.EnableMovieProviders;
+            }
+
+            return false;
+        }
+
+        public LiveTvProgramLookupInfo GetLookupInfo()
+        {
+            var info = GetItemLookupInfo<LiveTvProgramLookupInfo>();
+            info.IsMovie = IsMovie; 
+            return info;
+        }
     }
 }

+ 6 - 0
MediaBrowser.Controller/LiveTv/ProgramInfo.cs

@@ -145,6 +145,12 @@ namespace MediaBrowser.Controller.LiveTv
         /// <value><c>true</c> if this instance is premiere; otherwise, <c>false</c>.</value>
         public bool IsPremiere { get; set;  }
 
+        /// <summary>
+        /// Gets or sets the production year.
+        /// </summary>
+        /// <value>The production year.</value>
+        public int? ProductionYear { get; set; }
+        
         public ProgramInfo()
         {
             Genres = new List<string>();

+ 1 - 0
MediaBrowser.Controller/MediaBrowser.Controller.csproj

@@ -293,6 +293,7 @@
     <Compile Include="Providers\IRemoteSearchProvider.cs" />
     <Compile Include="Providers\ISeriesOrderProvider.cs" />
     <Compile Include="Providers\ItemInfo.cs" />
+    <Compile Include="Providers\LiveTvProgramLookupInfo.cs" />
     <Compile Include="Providers\LocalImageInfo.cs" />
     <Compile Include="Providers\LocalMetadataResult.cs" />
     <Compile Include="Providers\MetadataRefreshMode.cs" />

+ 9 - 0
MediaBrowser.Controller/Providers/LiveTvProgramLookupInfo.cs

@@ -0,0 +1,9 @@
+using System;
+
+namespace MediaBrowser.Controller.Providers
+{
+    public class LiveTvProgramLookupInfo : ItemLookupInfo
+    {
+        public Boolean IsMovie { get; set; }
+    }
+}

+ 1 - 0
MediaBrowser.Model/LiveTv/LiveTvOptions.cs

@@ -3,5 +3,6 @@
     public class LiveTvOptions
     {
         public int? GuideDays { get; set; }
+        public bool EnableMovieProviders { get; set; }
     }
 }

+ 62 - 9
MediaBrowser.Model/LiveTv/ProgramQuery.cs

@@ -1,4 +1,5 @@
-using System;
+using MediaBrowser.Model.Entities;
+using System;
 
 namespace MediaBrowser.Model.LiveTv
 {
@@ -7,11 +8,18 @@ namespace MediaBrowser.Model.LiveTv
     /// </summary>
     public class ProgramQuery
     {
+        public ProgramQuery()
+        {
+            ChannelIds = new string[] { };
+            SortBy = new string[] { };
+            Genres = new string[] { };
+        }
+
         /// <summary>
-        /// Gets or sets the channel identifier.
+        /// Gets or sets the channel ids.
         /// </summary>
-        /// <value>The channel identifier.</value>
-        public string[] ChannelIdList { get; set; }
+        /// <value>The channel ids.</value>
+        public string[] ChannelIds { get; set; }
 
         /// <summary>
         /// Gets or sets the user identifier.
@@ -19,19 +27,64 @@ namespace MediaBrowser.Model.LiveTv
         /// <value>The user identifier.</value>
         public string UserId { get; set; }
 
+        /// <summary>
+        /// The earliest date for which a program starts to return
+        /// </summary>
         public DateTime? MinStartDate { get; set; }
 
+        /// <summary>
+        /// The latest date for which a program starts to return
+        /// </summary>
         public DateTime? MaxStartDate { get; set; }
 
+        /// <summary>
+        /// The earliest date for which a program ends to return
+        /// </summary>
         public DateTime? MinEndDate { get; set; }
 
+        /// <summary>
+        /// The latest date for which a program ends to return
+        /// </summary>
         public DateTime? MaxEndDate { get; set; }
 
+        /// <summary>
+        /// Used to specific whether to return movies or not
+        /// </summary>
+        /// <remarks>If set to null, all programs will be returned</remarks>
+        public bool? IsMovie { get; set; }
+
+        /// <summary>
+        /// Skips over a given number of items within the results. Use for paging.
+        /// </summary>
+        public int? StartIndex { get; set; }
+
+        /// <summary>
+        /// Gets or sets a value indicating whether this instance has aired.
+        /// </summary>
+        /// <value><c>null</c> if [has aired] contains no value, <c>true</c> if [has aired]; otherwise, <c>false</c>.</value>
         public bool? HasAired { get; set; }
 
-        public ProgramQuery()
-        {
-            ChannelIdList = new string[] { };
-        }
+        /// <summary>
+        /// The maximum number of items to return
+        /// </summary>
+        public int? Limit { get; set; }
+
+        /// <summary>
+        /// What to sort the results by
+        /// </summary>
+        /// <value>The sort by.</value>
+        public string[] SortBy { get; set; }
+
+        /// <summary>
+        /// The sort order to return results with
+        /// </summary>
+        /// <value>The sort order.</value>
+        public SortOrder? SortOrder { get; set; }
+
+        /// <summary>
+        /// Limit results to items containing specific genres
+        /// </summary>
+        /// <value>The genres.</value>
+        public string[] Genres { get; set; }
     }
-}
+}

+ 6 - 0
MediaBrowser.Model/LiveTv/RecommendedProgramQuery.cs

@@ -25,5 +25,11 @@
         /// </summary>
         /// <value>The limit.</value>
         public int? Limit { get; set; }
+
+        /// <summary>
+        /// Gets or sets a value indicating whether this instance is movie.
+        /// </summary>
+        /// <value><c>null</c> if [is movie] contains no value, <c>true</c> if [is movie]; otherwise, <c>false</c>.</value>
+        public bool? IsMovie { get; set; }
     }
 }

+ 7 - 4
MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs

@@ -7,14 +7,16 @@ using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Providers.Manager;
 using System.Collections.Generic;
-using System.Threading;
-using System.Threading.Tasks;
 
 namespace MediaBrowser.Providers.LiveTv
 {
-    public class ProgramMetadataService : MetadataService<LiveTvProgram, ItemLookupInfo>
+    public class ProgramMetadataService : MetadataService<LiveTvProgram, LiveTvProgramLookupInfo>
     {
-        public ProgramMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager)
+        public ProgramMetadataService(
+            IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager,
+            IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager)
+            : base(
+                serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager)
         {
         }
 
@@ -25,6 +27,7 @@ namespace MediaBrowser.Providers.LiveTv
         /// <param name="target">The target.</param>
         /// <param name="lockedFields">The locked fields.</param>
         /// <param name="replaceData">if set to <c>true</c> [replace data].</param>
+        /// <param name="mergeMetadataSettings"></param>
         protected override void MergeData(LiveTvProgram source, LiveTvProgram target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
         {
             ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);

+ 4 - 4
MediaBrowser.Providers/Manager/ProviderManager.cs

@@ -312,7 +312,7 @@ namespace MediaBrowser.Providers.Manager
 
                 if (provider is IRemoteMetadataProvider)
                 {
-                    if (!ConfigurationManager.Configuration.EnableInternetProviders)
+                    if (!item.IsInternetMetadataEnabled())
                     {
                         return false;
                     }
@@ -360,7 +360,7 @@ namespace MediaBrowser.Providers.Manager
 
                     if (provider is IRemoteImageProvider)
                     {
-                        if (!ConfigurationManager.Configuration.EnableInternetProviders)
+                        if (!item.IsInternetMetadataEnabled())
                         {
                             return false;
                         }
@@ -515,7 +515,7 @@ namespace MediaBrowser.Providers.Manager
                 Type = MetadataPluginType.LocalMetadataProvider
             }));
 
-            if (ConfigurationManager.Configuration.EnableInternetProviders)
+            if (item.IsInternetMetadataEnabled())
             {
                 // Fetchers
                 list.AddRange(providers.Where(i => (i is IRemoteMetadataProvider)).Select(i => new MetadataPlugin
@@ -547,7 +547,7 @@ namespace MediaBrowser.Providers.Manager
                 Type = MetadataPluginType.LocalImageProvider
             }));
 
-            var enableInternet = ConfigurationManager.Configuration.EnableInternetProviders;
+            var enableInternet = item.IsInternetMetadataEnabled();
 
             // Fetchers
             list.AddRange(imageProviders.Where(i => i is IDynamicImageProvider || (enableInternet && i is IRemoteImageProvider)).Select(i => new MetadataPlugin

+ 1 - 0
MediaBrowser.Providers/MediaBrowser.Providers.csproj

@@ -108,6 +108,7 @@
     <Compile Include="MediaInfo\SubtitleDownloader.cs" />
     <Compile Include="MediaInfo\SubtitleResolver.cs" />
     <Compile Include="MediaInfo\SubtitleScheduledTask.cs" />
+    <Compile Include="Movies\LiveTvMovieDbProvider.cs" />
     <Compile Include="Movies\MovieDbTrailerProvider.cs" />
     <Compile Include="Movies\MovieExternalIds.cs" />
     <Compile Include="Movies\GenericMovieDbInfo.cs" />

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

@@ -17,7 +17,7 @@ using System.Threading.Tasks;
 namespace MediaBrowser.Providers.Movies
 {
     public class GenericMovieDbInfo<T>
-        where T : Video, new()
+        where T : BaseItem, new()
     {
         private readonly ILogger _logger;
         private readonly IJsonSerializer _jsonSerializer;

+ 43 - 0
MediaBrowser.Providers/Movies/LiveTvMovieDbProvider.cs

@@ -0,0 +1,43 @@
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Providers;
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Providers.Movies
+{
+    public class LiveTvMovieDbProvider : IRemoteMetadataProvider<LiveTvProgram, LiveTvProgramLookupInfo>, IDisposable, IHasOrder
+    {
+        public Task<IEnumerable<RemoteSearchResult>> GetSearchResults(LiveTvProgramLookupInfo searchInfo, CancellationToken cancellationToken)
+        {
+            return MovieDbProvider.Current.GetMovieSearchResults(searchInfo, cancellationToken);
+        }
+
+        public Task<MetadataResult<LiveTvProgram>> GetMetadata(LiveTvProgramLookupInfo info, CancellationToken cancellationToken)
+        {
+            return MovieDbProvider.Current.GetItemMetadata<LiveTvProgram>(info, cancellationToken);
+        }
+
+        public string Name
+        {
+            get { return "LiveTvMovieDbProvider"; }
+        }
+
+        public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
+        {
+            return MovieDbProvider.Current.GetImageResponse(url, cancellationToken);
+        }
+
+        public void Dispose()
+        {
+        }
+
+        public int Order
+        {
+            get { return 1; }
+        }
+    }
+}

+ 8 - 0
MediaBrowser.Providers/Movies/MovieDbImageProvider.cs

@@ -2,6 +2,7 @@
 using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Channels;
 using MediaBrowser.Model.Dto;
@@ -47,6 +48,13 @@ namespace MediaBrowser.Providers.Movies
                 return true;
             }
 
+            // Supports images for tv movies
+            var tvProgram = item as LiveTvProgram;
+            if (tvProgram != null && tvProgram.IsMovie)
+            {
+                return true;
+            }
+
             // Don't support local trailers
             return item is Movie || item is MusicVideo;
         }

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

@@ -113,7 +113,7 @@ namespace MediaBrowser.Providers.Movies
         }
 
         public Task<MetadataResult<T>> GetItemMetadata<T>(ItemLookupInfo id, CancellationToken cancellationToken)
-            where T : Video, new()
+            where T : BaseItem, new()
         {
             var movieDb = new GenericMovieDbInfo<T>(_logger, _jsonSerializer, _libraryManager);
 

+ 8 - 0
MediaBrowser.Providers/Omdb/OmdbImageProvider.cs

@@ -3,6 +3,7 @@ using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Channels;
 using MediaBrowser.Model.Entities;
@@ -108,6 +109,13 @@ namespace MediaBrowser.Providers.Omdb
                 }
             }
 
+            // Supports images for tv movies
+            var tvProgram = item as LiveTvProgram;
+            if (tvProgram != null && tvProgram.IsMovie)
+            {
+                return true;
+            }
+
             return item is Movie;
         }
 

+ 54 - 34
MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs

@@ -45,6 +45,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
         private readonly ILibraryManager _libraryManager;
         private readonly ITaskManager _taskManager;
         private readonly IJsonSerializer _jsonSerializer;
+        private readonly IProviderManager _providerManager;
 
         private readonly IDtoService _dtoService;
         private readonly ILocalizationManager _localization;
@@ -62,7 +63,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
         private readonly SemaphoreSlim _refreshSemaphore = new SemaphoreSlim(1, 1);
 
-        public LiveTvManager(IApplicationHost appHost, IServerConfigurationManager config, IFileSystem fileSystem, ILogger logger, IItemRepository itemRepo, IImageProcessor imageProcessor, IUserDataManager userDataManager, IDtoService dtoService, IUserManager userManager, ILibraryManager libraryManager, ITaskManager taskManager, ILocalizationManager localization, IJsonSerializer jsonSerializer)
+        public LiveTvManager(IApplicationHost appHost, IServerConfigurationManager config, IFileSystem fileSystem, ILogger logger, IItemRepository itemRepo, IImageProcessor imageProcessor, IUserDataManager userDataManager, IDtoService dtoService, IUserManager userManager, ILibraryManager libraryManager, ITaskManager taskManager, ILocalizationManager localization, IJsonSerializer jsonSerializer, IProviderManager providerManager)
         {
             _config = config;
             _fileSystem = fileSystem;
@@ -73,6 +74,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             _taskManager = taskManager;
             _localization = localization;
             _jsonSerializer = jsonSerializer;
+            _providerManager = providerManager;
             _dtoService = dtoService;
             _userDataManager = userDataManager;
 
@@ -237,7 +239,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
             foreach (var channel in internalResult.Items)
             {
-                var currentProgram = await GetCurrentProgram(channel.ExternalId, cancellationToken).ConfigureAwait(false);
+                var currentProgram = GetCurrentProgram(channel.ExternalId);
 
                 returnList.Add(_tvDtoService.GetChannelInfoDto(channel, currentProgram, user));
             }
@@ -261,7 +263,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             return _libraryManager.GetItemById(id) as LiveTvChannel;
         }
 
-        public async Task<LiveTvProgram> GetInternalProgram(string id, CancellationToken cancellationToken)
+        private LiveTvProgram GetInternalProgram(string id)
         {
             var guid = new Guid(id);
 
@@ -271,37 +273,26 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
             if (obj != null)
             {
-                await RefreshIfNeeded(obj, cancellationToken).ConfigureAwait(false);
+                RefreshIfNeeded(obj);
             }
             return obj;
         }
 
-        private Task RefreshIfNeeded(IEnumerable<LiveTvProgram> programs, CancellationToken cancellationToken)
+        private void RefreshIfNeeded(LiveTvProgram program)
         {
-            var list = programs.ToList();
-
-            Task.Run(async () =>
+            if (!_refreshedPrograms.ContainsKey(program.Id))
             {
-                foreach (var program in list)
-                {
-                    await RefreshIfNeeded(program, CancellationToken.None).ConfigureAwait(false);
-                }
-
-            }, cancellationToken);
-
-            return Task.FromResult(true);
+                _refreshedPrograms.TryAdd(program.Id, true);
+                _providerManager.QueueRefresh(program.Id, new MetadataRefreshOptions());
+            }
         }
 
-        private readonly Task _cachedTask = Task.FromResult(true);
-        private Task RefreshIfNeeded(LiveTvProgram program, CancellationToken cancellationToken)
+        private void RefreshIfNeeded(IEnumerable<LiveTvProgram> programs)
         {
-            if (!_refreshedPrograms.ContainsKey(program.Id))
+            foreach (var program in programs)
             {
-                _refreshedPrograms.TryAdd(program.Id, true);
-                return program.RefreshMetadata(cancellationToken);
+                RefreshIfNeeded(program);
             }
-
-            return _cachedTask;
         }
 
         public async Task<ILiveTvRecording> GetInternalRecording(string id, CancellationToken cancellationToken)
@@ -528,6 +519,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             item.ProviderImageUrl = info.ImageUrl;
             item.RunTimeTicks = (info.EndDate - info.StartDate).Ticks;
             item.StartDate = info.StartDate;
+            item.ProductionYear = info.ProductionYear;
 
             return item;
         }
@@ -607,7 +599,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
         public async Task<ProgramInfoDto> GetProgram(string id, CancellationToken cancellationToken, User user = null)
         {
-            var program = await GetInternalProgram(id, cancellationToken).ConfigureAwait(false);
+            var program = GetInternalProgram(id);
 
             var channel = GetChannel(program);
 
@@ -656,9 +648,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                 programs = programs.Where(i => i.HasAired == val);
             }
 
-            if (query.ChannelIdList.Length > 0)
+            if (query.ChannelIds.Length > 0)
             {
-                var guids = query.ChannelIdList.Select(i => new Guid(i)).ToList();
+                var guids = query.ChannelIds.Select(i => new Guid(i)).ToList();
 
                 programs = programs.Where(i =>
                 {
@@ -672,7 +664,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             }
 
             var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId);
-
             if (user != null)
             {
                 // Avoid implicitly captured closure
@@ -680,6 +671,30 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                 programs = programs.Where(i => i.IsVisible(currentUser));
             }
 
+            // Apply genre filter
+            if (query.Genres.Length > 0)
+            {
+                programs = programs.Where(p => p.Genres.Any(g => query.Genres.Contains(g, StringComparer.OrdinalIgnoreCase)));
+            }
+
+            if (query.IsMovie.HasValue)
+            {
+                programs = programs.Where(p => p.IsMovie == query.IsMovie);
+            }
+
+            programs = _libraryManager.Sort(programs, user, query.SortBy, query.SortOrder ?? SortOrder.Ascending)
+                .Cast<LiveTvProgram>();
+
+            if (query.StartIndex.HasValue)
+            {
+                programs = programs.Skip(query.StartIndex.Value);
+            }
+
+            if (query.Limit.HasValue)
+            {
+                programs = programs.Take(query.Limit.Value);
+            }
+
             var programList = programs.ToList();
 
             var returnArray = programList
@@ -691,7 +706,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                 })
                 .ToArray();
 
-            await RefreshIfNeeded(programList, cancellationToken).ConfigureAwait(false);
+            RefreshIfNeeded(programList);
 
             await AddRecordingInfo(returnArray, cancellationToken).ConfigureAwait(false);
 
@@ -726,6 +741,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                 programs = programs.Where(i => i.HasAired == val);
             }
 
+            if (query.IsMovie.HasValue)
+            {
+                programs = programs.Where(p => p.IsMovie == query.IsMovie.Value);
+            }
+
             var serviceName = ActiveService.Name;
 
             var programList = programs.ToList();
@@ -747,7 +767,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
             programList = programs.ToList();
 
-            await RefreshIfNeeded(programList, cancellationToken).ConfigureAwait(false);
+            RefreshIfNeeded(programList);
 
             var returnArray = programList.ToArray();
 
@@ -1233,7 +1253,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             {
                 var program = string.IsNullOrEmpty(i.ProgramId) ?
                     null :
-                    await GetInternalProgram(_tvDtoService.GetInternalProgramId(service.Name, i.ProgramId).ToString("N"), cancellationToken).ConfigureAwait(false);
+                    GetInternalProgram(_tvDtoService.GetInternalProgramId(service.Name, i.ProgramId).ToString("N"));
 
                 var channel = string.IsNullOrEmpty(i.ChannelId) ? null : GetInternalChannel(_tvDtoService.GetInternalChannelId(service.Name, i.ChannelId));
 
@@ -1366,14 +1386,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv
         {
             var channel = GetInternalChannel(id);
 
-            var currentProgram = await GetCurrentProgram(channel.ExternalId, cancellationToken).ConfigureAwait(false);
+            var currentProgram = GetCurrentProgram(channel.ExternalId);
 
             var dto = _tvDtoService.GetChannelInfoDto(channel, currentProgram, user);
 
             return dto;
         }
 
-        private async Task<LiveTvProgram> GetCurrentProgram(string externalChannelId, CancellationToken cancellationToken)
+        private LiveTvProgram GetCurrentProgram(string externalChannelId)
         {
             var now = DateTime.UtcNow;
 
@@ -1385,7 +1405,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
             if (program != null)
             {
-                await RefreshIfNeeded(program, cancellationToken).ConfigureAwait(false);
+                RefreshIfNeeded(program);
             }
 
             return program;
@@ -1444,7 +1464,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
         public async Task<SeriesTimerInfoDto> GetNewTimerDefaults(string programId, CancellationToken cancellationToken)
         {
-            var program = await GetInternalProgram(programId, cancellationToken).ConfigureAwait(false);
+            var program = GetInternalProgram(programId);
             var programDto = await GetProgram(programId, cancellationToken).ConfigureAwait(false);
 
             var defaults = await GetNewTimerDefaultsInternal(cancellationToken, program).ConfigureAwait(false);

+ 3 - 1
MediaBrowser.Server.Implementations/Localization/Server/server.json

@@ -1389,5 +1389,7 @@
     "LabelTagFilterMode": "Mode:",
     "LabelTagFilterAllowModeHelp": "If allowed tags are used as part of a deeply nested folder structure, content that is tagged will require parent folders to be tagged as well.",
     "HeaderThisUserIsCurrentlyDisabled": "This user is currently disabled",
-    "MessageReenableUser": "See below to reenable"
+    "MessageReenableUser": "See below to reenable",
+    "LabelEnableInternetMetadataForTvPrograms": "Download internet metadata for:",
+    "OptionTVMovies": "TV Movies"
 }

+ 1 - 1
MediaBrowser.Server.Startup.Common/ApplicationHost.cs

@@ -495,7 +495,7 @@ namespace MediaBrowser.Server.Startup.Common
             PlaylistManager = new PlaylistManager(LibraryManager, FileSystemManager, LibraryMonitor, LogManager.GetLogger("PlaylistManager"), UserManager);
             RegisterSingleInstance<IPlaylistManager>(PlaylistManager);
 
-            LiveTvManager = new LiveTvManager(this, ServerConfigurationManager, FileSystemManager, Logger, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, LocalizationManager, JsonSerializer);
+            LiveTvManager = new LiveTvManager(this, ServerConfigurationManager, FileSystemManager, Logger, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, LocalizationManager, JsonSerializer, ProviderManager);
             RegisterSingleInstance(LiveTvManager);
 
             UserViewManager = new UserViewManager(LibraryManager, LocalizationManager, UserManager, ChannelManager, LiveTvManager, PlaylistManager, CollectionManager, ServerConfigurationManager);