Browse Source

convert programs and channels to new providers

Luke Pulverenti 11 năm trước cách đây
mục cha
commit
c0f606683a

+ 13 - 5
MediaBrowser.Controller/Providers/ILocalImageProvider.cs

@@ -3,6 +3,7 @@ using MediaBrowser.Controller.Entities;
 using MediaBrowser.Model.Entities;
 using System.Collections.Generic;
 using System.IO;
+using System.Threading;
 using System.Threading.Tasks;
 
 namespace MediaBrowser.Controller.Providers
@@ -28,19 +29,20 @@ namespace MediaBrowser.Controller.Providers
     public interface IDynamicImageProvider : ILocalImageProvider
     {
         /// <summary>
-        /// Gets the images.
+        /// Gets the supported images.
         /// </summary>
         /// <param name="item">The item.</param>
-        /// <returns>List{DynamicImageInfo}.</returns>
-        List<DynamicImageInfo> GetImageInfos(IHasImages item);
+        /// <returns>IEnumerable{ImageType}.</returns>
+        IEnumerable<ImageType> GetSupportedImages(IHasImages item);
 
         /// <summary>
         /// Gets the image.
         /// </summary>
         /// <param name="item">The item.</param>
-        /// <param name="info">The information.</param>
+        /// <param name="type">The type.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task{DynamicImageResponse}.</returns>
-        Task<DynamicImageResponse> GetImage(IHasImages item, DynamicImageInfo info);
+        Task<DynamicImageResponse> GetImage(IHasImages item, ImageType type, CancellationToken cancellationToken);
     }
 
     public class DynamicImageInfo
@@ -54,5 +56,11 @@ namespace MediaBrowser.Controller.Providers
         public string Path { get; set; }
         public Stream Stream { get; set; }
         public ImageFormat Format { get; set; }
+        public bool HasImage { get; set; }
+
+        public void SetFormatFromMimeType(string mimeType)
+        {
+            
+        }
     }
 }

+ 6 - 0
MediaBrowser.Providers/BaseXmlProvider.cs

@@ -1,6 +1,7 @@
 using MediaBrowser.Common.IO;
 using MediaBrowser.Controller.Providers;
 using System;
+using System.IO;
 using System.Threading;
 
 namespace MediaBrowser.Providers
@@ -24,5 +25,10 @@ namespace MediaBrowser.Providers
 
             return FileSystem.GetLastWriteTimeUtc(path) > date;
         }
+
+        public bool HasLocalMetadata(IHasMetadata item)
+        {
+            return File.Exists(GetXmlPath(item.Path));
+        }
     }
 }

+ 37 - 0
MediaBrowser.Providers/LiveTv/ChannelMetadataService.cs

@@ -0,0 +1,37 @@
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.Providers;
+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 ChannelMetadataService : MetadataService<LiveTvChannel>
+    {
+        private readonly ILibraryManager _libraryManager;
+
+        public ChannelMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, ILibraryManager libraryManager)
+            : base(serverConfigurationManager, logger, providerManager, providerRepo)
+        {
+            _libraryManager = libraryManager;
+        }
+
+        /// <summary>
+        /// Merges the specified source.
+        /// </summary>
+        protected override void MergeData(LiveTvChannel source, LiveTvChannel target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
+        {
+            ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
+        }
+
+        protected override Task SaveItem(LiveTvChannel item, ItemUpdateType reason, CancellationToken cancellationToken)
+        {
+            return _libraryManager.UpdateItem(item, reason, cancellationToken);
+        }
+    }
+}

+ 0 - 91
MediaBrowser.Providers/LiveTv/ChannelProviderFromXml.cs

@@ -1,91 +0,0 @@
-using MediaBrowser.Common.IO;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.LiveTv;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Logging;
-using System;
-using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Providers.LiveTv
-{
-    class ChannelProviderFromXml : BaseMetadataProvider
-    {
-        private readonly IFileSystem _fileSystem;
-
-        public ChannelProviderFromXml(ILogManager logManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem)
-            : base(logManager, configurationManager)
-        {
-            _fileSystem = fileSystem;
-        }
-
-        /// <summary>
-        /// Supportses the specified item.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
-        public override bool Supports(BaseItem item)
-        {
-            return item is LiveTvChannel;
-        }
-
-        /// <summary>
-        /// Gets the priority.
-        /// </summary>
-        /// <value>The priority.</value>
-        public override MetadataProviderPriority Priority
-        {
-            get { return MetadataProviderPriority.Second; }
-        }
-
-        private const string XmlFileName = "channel.xml";
-        protected override bool NeedsRefreshBasedOnCompareDate(BaseItem item, BaseProviderInfo providerInfo)
-        {
-            var xml = item.ResolveArgs.GetMetaFileByPath(Path.Combine(item.MetaLocation, XmlFileName));
-
-            if (xml == null)
-            {
-                return false;
-            }
-
-            return _fileSystem.GetLastWriteTimeUtc(xml) > item.DateLastSaved;
-        }
-
-        /// <summary>
-        /// Fetches metadata and returns true or false indicating if any work that requires persistence was done
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="force">if set to <c>true</c> [force].</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task{System.Boolean}.</returns>
-        public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
-        {
-            cancellationToken.ThrowIfCancellationRequested();
-
-            var metadataFile = item.ResolveArgs.GetMetaFileByPath(Path.Combine(item.MetaLocation, XmlFileName));
-
-            if (metadataFile != null)
-            {
-                var path = metadataFile.FullName;
-
-                await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
-
-                try
-                {
-                    new BaseItemXmlParser<LiveTvChannel>(Logger).Fetch((LiveTvChannel)item, path, cancellationToken);
-                }
-                finally
-                {
-                    XmlParsingResourcePool.Release();
-                }
-
-                SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
-                return true;
-            }
-
-            return false;
-        }
-    }
-}

+ 59 - 0
MediaBrowser.Providers/LiveTv/ChannelXmlProvider.cs

@@ -0,0 +1,59 @@
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Logging;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Providers.LiveTv
+{
+    public class ChannelXmlProvider : BaseXmlProvider, ILocalMetadataProvider<LiveTvChannel>
+    {
+        private readonly ILogger _logger;
+
+        public ChannelXmlProvider(IFileSystem fileSystem, ILogger logger)
+            : base(fileSystem)
+        {
+            _logger = logger;
+        }
+
+        public async Task<MetadataResult<LiveTvChannel>> GetMetadata(string path, CancellationToken cancellationToken)
+        {
+            path = GetXmlPath(path);
+
+            var result = new MetadataResult<LiveTvChannel>();
+
+            await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
+
+            try
+            {
+                var item = new LiveTvChannel();
+
+                new BaseItemXmlParser<LiveTvChannel>(_logger).Fetch(item, path, cancellationToken);
+                result.HasMetadata = true;
+                result.Item = item;
+            }
+            catch (FileNotFoundException)
+            {
+                result.HasMetadata = false;
+            }
+            finally
+            {
+                XmlParsingResourcePool.Release();
+            }
+
+            return result;
+        }
+
+        public string Name
+        {
+            get { return "Media Browser Xml"; }
+        }
+
+        protected override string GetXmlPath(string path)
+        {
+            return Path.Combine(path, "channel.xml");
+        }
+    }
+}

+ 41 - 0
MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs

@@ -0,0 +1,41 @@
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.Providers;
+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>
+    {
+        private readonly ILibraryManager _libraryManager;
+
+        public ProgramMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, ILibraryManager libraryManager)
+            : base(serverConfigurationManager, logger, providerManager, providerRepo)
+        {
+            _libraryManager = libraryManager;
+        }
+
+        /// <summary>
+        /// Merges the specified source.
+        /// </summary>
+        /// <param name="source">The source.</param>
+        /// <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>
+        protected override void MergeData(LiveTvProgram source, LiveTvProgram target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
+        {
+            ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
+        }
+
+        protected override Task SaveItem(LiveTvProgram item, ItemUpdateType reason, CancellationToken cancellationToken)
+        {
+            return _libraryManager.UpdateItem(item, reason, cancellationToken);
+        }
+    }
+}

+ 9 - 14
MediaBrowser.Providers/Manager/ItemImageProvider.cs

@@ -87,27 +87,22 @@ namespace MediaBrowser.Providers.Manager
 
             try
             {
-                var images = provider.GetImageInfos(item);
+                var images = provider.GetSupportedImages(item);
 
-                foreach (var image in images)
+                foreach (var imageType in images)
                 {
-                    if (!item.HasImage(image.Type))
+                    if (!item.HasImage(imageType))
                     {
-                        var imageSource = await provider.GetImage(item, image).ConfigureAwait(false);
+                        var response = await provider.GetImage(item, imageType, cancellationToken).ConfigureAwait(false);
 
-                        // See if the provider returned an image path or a stream
-                        if (!string.IsNullOrEmpty(imageSource.Path))
+                        if (response.HasImage)
                         {
-                            item.SetImagePath(image.Type, imageSource.Path);
-                        }
-                        else
-                        {
-                            var mimeType = "image/" + imageSource.Format.ToString().ToLower();
+                            var mimeType = "image/" + response.Format.ToString().ToLower();
 
-                            await _providerManager.SaveImage((BaseItem)item, imageSource.Stream, mimeType, image.Type, null, Guid.NewGuid().ToString(), cancellationToken).ConfigureAwait(false);
-                        }
+                            await _providerManager.SaveImage((BaseItem)item, response.Stream, mimeType, imageType, null, Guid.NewGuid().ToString(), cancellationToken).ConfigureAwait(false);
 
-                        result.UpdateType = result.UpdateType | ItemUpdateType.ImageUpdate;
+                            result.UpdateType = result.UpdateType | ItemUpdateType.ImageUpdate;
+                        }
                     }
                 }
             }

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

@@ -67,6 +67,9 @@
     <Compile Include="All\LocalImageProvider.cs" />
     <Compile Include="GameGenres\GameGenreMetadataService.cs" />
     <Compile Include="Genres\GenreMetadataService.cs" />
+    <Compile Include="LiveTv\ChannelMetadataService.cs" />
+    <Compile Include="LiveTv\ChannelXmlProvider.cs" />
+    <Compile Include="LiveTv\ProgramMetadataService.cs" />
     <Compile Include="Manager\ImageSaver.cs" />
     <Compile Include="Manager\ItemImageProvider.cs" />
     <Compile Include="Manager\ProviderManager.cs" />
@@ -84,7 +87,6 @@
     <Compile Include="GameGenres\GameGenreImageProvider.cs" />
     <Compile Include="Genres\GenreImageProvider.cs" />
     <Compile Include="ImagesByName\ImageUtils.cs" />
-    <Compile Include="LiveTv\ChannelProviderFromXml.cs" />
     <Compile Include="MediaInfo\AudioImageProvider.cs" />
     <Compile Include="MediaInfo\BaseFFProbeProvider.cs" />
     <Compile Include="MediaInfo\FFProbeAudioInfoProvider.cs" />

+ 0 - 5
MediaBrowser.Providers/People/PersonXmlProvider.cs

@@ -55,10 +55,5 @@ namespace MediaBrowser.Providers.People
         {
             return Path.Combine(path, "person.xml");
         }
-
-        public bool HasLocalMetadata(IHasMetadata item)
-        {
-            return File.Exists(GetXmlPath(item.Path));
-        }
     }
 }

+ 3 - 3
MediaBrowser.Server.Implementations/Library/LibraryManager.cs

@@ -1462,13 +1462,13 @@ namespace MediaBrowser.Server.Implementations.Library
 
                 var semaphore = _fileLocks.GetOrAdd(path, key => new SemaphoreSlim(1, 1));
 
-                var directoryWatchers = _libraryMonitorFactory();
+                var libraryMonitor = _libraryMonitorFactory();
 
                 await semaphore.WaitAsync().ConfigureAwait(false);
 
                 try
                 {
-                    directoryWatchers.ReportFileSystemChangeBeginning(path);
+                    libraryMonitor.ReportFileSystemChangeBeginning(path);
                     saver.Save(item, CancellationToken.None);
                 }
                 catch (Exception ex)
@@ -1477,7 +1477,7 @@ namespace MediaBrowser.Server.Implementations.Library
                 }
                 finally
                 {
-                    directoryWatchers.ReportFileSystemChangeComplete(path, false);
+                    libraryMonitor.ReportFileSystemChangeComplete(path, false);
                     semaphore.Release();
                 }
             }

+ 43 - 86
MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs

@@ -1,154 +1,111 @@
-using MediaBrowser.Common.IO;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
-using MediaBrowser.Model.Net;
 using System;
-using System.IO;
+using System.Collections.Generic;
 using System.Linq;
-using System.Net;
 using System.Threading;
 using System.Threading.Tasks;
 
 namespace MediaBrowser.Server.Implementations.LiveTv
 {
-    public class ChannelImageProvider : BaseMetadataProvider
+    public class ChannelImageProvider : IDynamicImageProvider, IHasChangeMonitor
     {
         private readonly ILiveTvManager _liveTvManager;
-        private readonly IProviderManager _providerManager;
-        private readonly IFileSystem _fileSystem;
         private readonly IHttpClient _httpClient;
+        private readonly ILogger _logger;
 
-        public ChannelImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager, ILiveTvManager liveTvManager, IProviderManager providerManager, IFileSystem fileSystem, IHttpClient httpClient)
-            : base(logManager, configurationManager)
+        public ChannelImageProvider(ILiveTvManager liveTvManager, IHttpClient httpClient, ILogger logger)
         {
             _liveTvManager = liveTvManager;
-            _providerManager = providerManager;
-            _fileSystem = fileSystem;
             _httpClient = httpClient;
+            _logger = logger;
         }
 
-        public override bool Supports(BaseItem item)
+        public IEnumerable<ImageType> GetSupportedImages(IHasImages item)
         {
-            return item is LiveTvChannel;
-        }
-
-        protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
-        {
-            return !item.HasImage(ImageType.Primary);
+            return new[] { ImageType.Primary };
         }
 
-        public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
+        public async Task<DynamicImageResponse> GetImage(IHasImages item, ImageType type, CancellationToken cancellationToken)
         {
-            if (item.HasImage(ImageType.Primary))
-            {
-                SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
-                return true;
-            }
-
-            var changed = true;
-
-            try
-            {
-                changed = await DownloadImage((LiveTvChannel)item, cancellationToken).ConfigureAwait(false);
-            }
-            catch (HttpException ex)
-            {
-                // Don't fail the provider on a 404
-                if (!ex.StatusCode.HasValue || ex.StatusCode.Value != HttpStatusCode.NotFound)
-                {
-                    throw;
-                }
-            }
-
-            if (changed)
-            {
-                SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
-            }
-
-            return changed;
-        }
+            var liveTvItem = (LiveTvChannel)item;
 
-        private async Task<bool> DownloadImage(LiveTvChannel item, CancellationToken cancellationToken)
-        {
-            Stream imageStream = null;
-            string contentType = null;
+            var imageResponse = new DynamicImageResponse();
 
-            if (!string.IsNullOrEmpty(item.ProviderImagePath))
+            if (!string.IsNullOrEmpty(liveTvItem.ProviderImagePath))
             {
-                contentType = "image/" + Path.GetExtension(item.ProviderImagePath).ToLower();
-                imageStream = _fileSystem.GetFileStream(item.ProviderImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, true);
+                imageResponse.Path = liveTvItem.ProviderImagePath;
+                imageResponse.HasImage = true;
             }
-            else if (!string.IsNullOrEmpty(item.ProviderImageUrl))
+            else if (!string.IsNullOrEmpty(liveTvItem.ProviderImageUrl))
             {
                 var options = new HttpRequestOptions
                 {
                     CancellationToken = cancellationToken,
-                    Url = item.ProviderImageUrl
+                    Url = liveTvItem.ProviderImageUrl
                 };
 
                 var response = await _httpClient.GetResponse(options).ConfigureAwait(false);
 
-                if (!response.ContentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase))
+                if (response.ContentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase))
                 {
-                    Logger.Error("Provider did not return an image content type.");
-                    return false;
+                    imageResponse.HasImage = true;
+                    imageResponse.Stream = response.Content;
+                    imageResponse.SetFormatFromMimeType(response.ContentType);
+                }
+                else
+                {
+                    _logger.Error("Provider did not return an image content type.");
                 }
-
-                imageStream = response.Content;
-                contentType = response.ContentType;
             }
-            else if (item.HasProviderImage ?? true)
+            else if (liveTvItem.HasProviderImage ?? true)
             {
-                var service = _liveTvManager.Services.FirstOrDefault(i => string.Equals(i.Name, item.ServiceName, StringComparison.OrdinalIgnoreCase));
+                var service = _liveTvManager.Services.FirstOrDefault(i => string.Equals(i.Name, liveTvItem.ServiceName, StringComparison.OrdinalIgnoreCase));
 
                 if (service != null)
                 {
                     try
                     {
-                        var response = await service.GetChannelImageAsync(item.ExternalId, cancellationToken).ConfigureAwait(false);
+                        var response = await service.GetChannelImageAsync(liveTvItem.ExternalId, cancellationToken).ConfigureAwait(false);
 
                         if (response != null)
                         {
-                            imageStream = response.Stream;
-                            contentType = "image/" + response.Format.ToString().ToLower();
+                            imageResponse.HasImage = true;
+                            imageResponse.Stream = response.Stream;
+                            imageResponse.Format = response.Format;
                         }
                     }
                     catch (NotImplementedException)
                     {
-                        return false;
                     }
                 }
             }
 
-            if (imageStream != null)
-            {
-                // Dummy up the original url
-                var url = item.ServiceName + item.ExternalId;
+            return imageResponse;
+        }
 
-                await _providerManager.SaveImage(item, imageStream, contentType, ImageType.Primary, null, url, cancellationToken).ConfigureAwait(false);
-                return true;
-            }
+        public string Name
+        {
+            get { return "Live TV Service Provider"; }
+        }
 
-            return false;
+        public bool Supports(IHasImages item)
+        {
+            return item is LiveTvChannel;
         }
 
-        public override MetadataProviderPriority Priority
+        public int Order
         {
-            get { return MetadataProviderPriority.Second; }
+            get { return 0; }
         }
 
-        public override ItemUpdateType ItemUpdateType
+        public bool HasChanged(IHasMetadata item, DateTime date)
         {
-            get
-            {
-                return ItemUpdateType.ImageUpdate;
-            }
+            return !item.HasImage(ImageType.Primary) && (DateTime.UtcNow - date).TotalDays >= 1;
         }
     }
 }

+ 43 - 86
MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs

@@ -1,154 +1,111 @@
-using MediaBrowser.Common.IO;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
-using MediaBrowser.Model.Net;
 using System;
-using System.IO;
+using System.Collections.Generic;
 using System.Linq;
-using System.Net;
 using System.Threading;
 using System.Threading.Tasks;
 
 namespace MediaBrowser.Server.Implementations.LiveTv
 {
-    public class ProgramImageProvider : BaseMetadataProvider
+    public class ProgramImageProvider : IDynamicImageProvider, IHasChangeMonitor
     {
         private readonly ILiveTvManager _liveTvManager;
-        private readonly IProviderManager _providerManager;
-        private readonly IFileSystem _fileSystem;
         private readonly IHttpClient _httpClient;
+        private readonly ILogger _logger;
 
-        public ProgramImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager, ILiveTvManager liveTvManager, IProviderManager providerManager, IFileSystem fileSystem, IHttpClient httpClient)
-            : base(logManager, configurationManager)
+        public ProgramImageProvider(ILiveTvManager liveTvManager, IHttpClient httpClient, ILogger logger)
         {
             _liveTvManager = liveTvManager;
-            _providerManager = providerManager;
-            _fileSystem = fileSystem;
             _httpClient = httpClient;
+            _logger = logger;
         }
 
-        public override bool Supports(BaseItem item)
+        public IEnumerable<ImageType> GetSupportedImages(IHasImages item)
         {
-            return item is LiveTvProgram;
-        }
-
-        protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
-        {
-            return !item.HasImage(ImageType.Primary);
+            return new[] { ImageType.Primary };
         }
 
-        public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
+        public async Task<DynamicImageResponse> GetImage(IHasImages item, ImageType type, CancellationToken cancellationToken)
         {
-            if (item.HasImage(ImageType.Primary))
-            {
-                SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
-                return true;
-            }
-
-            var changed = true;
-
-            try
-            {
-                changed = await DownloadImage((LiveTvProgram)item, cancellationToken).ConfigureAwait(false);
-            }
-            catch (HttpException ex)
-            {
-                // Don't fail the provider on a 404
-                if (!ex.StatusCode.HasValue || ex.StatusCode.Value != HttpStatusCode.NotFound)
-                {
-                    throw;
-                }
-            }
-
-            if (changed)
-            {
-                SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
-            }
-
-            return changed;
-        }
+            var liveTvItem = (LiveTvProgram)item;
 
-        private async Task<bool> DownloadImage(LiveTvProgram item, CancellationToken cancellationToken)
-        {
-            Stream imageStream = null;
-            string contentType = null;
+            var imageResponse = new DynamicImageResponse();
 
-            if (!string.IsNullOrEmpty(item.ProviderImagePath))
+            if (!string.IsNullOrEmpty(liveTvItem.ProviderImagePath))
             {
-                contentType = "image/" + Path.GetExtension(item.ProviderImagePath).ToLower();
-                imageStream = _fileSystem.GetFileStream(item.ProviderImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, true);
+                imageResponse.Path = liveTvItem.ProviderImagePath;
+                imageResponse.HasImage = true;
             }
-            else if (!string.IsNullOrEmpty(item.ProviderImageUrl))
+            else if (!string.IsNullOrEmpty(liveTvItem.ProviderImageUrl))
             {
                 var options = new HttpRequestOptions
                 {
                     CancellationToken = cancellationToken,
-                    Url = item.ProviderImageUrl
+                    Url = liveTvItem.ProviderImageUrl
                 };
 
                 var response = await _httpClient.GetResponse(options).ConfigureAwait(false);
 
-                if (!response.ContentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase))
+                if (response.ContentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase))
                 {
-                    Logger.Error("Provider did not return an image content type.");
-                    return false;
+                    imageResponse.HasImage = true;
+                    imageResponse.Stream = response.Content;
+                    imageResponse.SetFormatFromMimeType(response.ContentType);
+                }
+                else
+                {
+                    _logger.Error("Provider did not return an image content type.");
                 }
-
-                imageStream = response.Content;
-                contentType = response.ContentType;
             }
-            else if (item.HasProviderImage ?? true)
+            else if (liveTvItem.HasProviderImage ?? true)
             {
-                var service = _liveTvManager.Services.FirstOrDefault(i => string.Equals(i.Name, item.ServiceName, StringComparison.OrdinalIgnoreCase));
+                var service = _liveTvManager.Services.FirstOrDefault(i => string.Equals(i.Name, liveTvItem.ServiceName, StringComparison.OrdinalIgnoreCase));
 
                 if (service != null)
                 {
                     try
                     {
-                        var response = await service.GetProgramImageAsync(item.ExternalId, item.ExternalChannelId, cancellationToken).ConfigureAwait(false);
+                        var response = await service.GetProgramImageAsync(liveTvItem.ExternalId, liveTvItem.ExternalChannelId, cancellationToken).ConfigureAwait(false);
 
                         if (response != null)
                         {
-                            imageStream = response.Stream;
-                            contentType = "image/" + response.Format.ToString().ToLower();
+                            imageResponse.HasImage = true;
+                            imageResponse.Stream = response.Stream;
+                            imageResponse.Format = response.Format;
                         }
                     }
                     catch (NotImplementedException)
                     {
-                        return false;
                     }
                 }
             }
 
-            if (imageStream != null)
-            {
-                // Dummy up the original url
-                var url = item.ServiceName + item.ExternalId;
+            return imageResponse;
+        }
 
-                await _providerManager.SaveImage(item, imageStream, contentType, ImageType.Primary, null, url, cancellationToken).ConfigureAwait(false);
-                return true;
-            }
+        public string Name
+        {
+            get { return "Live TV Service Provider"; }
+        }
 
-            return false;
+        public bool Supports(IHasImages item)
+        {
+            return item is LiveTvProgram;
         }
 
-        public override MetadataProviderPriority Priority
+        public int Order
         {
-            get { return MetadataProviderPriority.Second; }
+            get { return 0; }
         }
 
-        public override ItemUpdateType ItemUpdateType
+        public bool HasChanged(IHasMetadata item, DateTime date)
         {
-            get
-            {
-                return ItemUpdateType.ImageUpdate;
-            }
+            return !item.HasImage(ImageType.Primary) && (DateTime.UtcNow - date).TotalHours >= 12;
         }
     }
 }