2
0
Эх сурвалжийг харах

Merge branch 'dev' of https://github.com/MediaBrowser/Emby into dev

Eric Reed 9 жил өмнө
parent
commit
8ef0596f0f
29 өөрчлөгдсөн 378 нэмэгдсэн , 150 устгасан
  1. 18 14
      Emby.Drawing/ImageProcessor.cs
  2. 6 3
      MediaBrowser.Api/Images/ImageService.cs
  3. 16 2
      MediaBrowser.Api/Social/SharingService.cs
  4. 5 5
      MediaBrowser.Controller/Drawing/IImageProcessor.cs
  5. 51 17
      MediaBrowser.Controller/Entities/BaseItem.cs
  6. 17 0
      MediaBrowser.Controller/Entities/IHasImages.cs
  7. 0 8
      MediaBrowser.Controller/Entities/IHasMetadata.cs
  8. 7 0
      MediaBrowser.Controller/Entities/ItemImageInfo.cs
  9. 9 0
      MediaBrowser.Controller/Library/ILibraryManager.cs
  10. 3 2
      MediaBrowser.Controller/LiveTv/ITunerHost.cs
  11. 2 0
      MediaBrowser.Controller/Providers/DynamicImageResponse.cs
  12. 4 0
      MediaBrowser.Model/Configuration/ServerConfiguration.cs
  13. 6 4
      MediaBrowser.Providers/Manager/ImageSaver.cs
  14. 20 6
      MediaBrowser.Providers/Manager/ItemImageProvider.cs
  15. 6 4
      MediaBrowser.Providers/Music/AlbumImageFromSongProvider.cs
  16. 4 18
      MediaBrowser.Server.Implementations/Channels/ChannelItemImageProvider.cs
  17. 1 1
      MediaBrowser.Server.Implementations/Dto/DtoService.cs
  18. 12 0
      MediaBrowser.Server.Implementations/Library/LibraryManager.cs
  19. 64 25
      MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
  20. 2 2
      MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
  21. 4 18
      MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs
  22. 1 1
      MediaBrowser.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs
  23. 39 5
      MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
  24. 2 3
      MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs
  25. 16 1
      MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs
  26. 9 6
      MediaBrowser.Server.Startup.Common/ApplicationHost.cs
  27. 1 0
      MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj
  28. 47 0
      MediaBrowser.Server.Startup.Common/Migrations/Release5767.cs
  29. 6 5
      MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs

+ 18 - 14
Emby.Drawing/ImageProcessor.cs

@@ -18,6 +18,7 @@ using System.Threading;
 using System.Threading.Tasks;
 using CommonIO;
 using Emby.Drawing.Common;
+using MediaBrowser.Controller.Library;
 
 namespace Emby.Drawing
 {
@@ -53,18 +54,20 @@ namespace Emby.Drawing
         private readonly IServerApplicationPaths _appPaths;
         private readonly IImageEncoder _imageEncoder;
         private readonly SemaphoreSlim _imageProcessingSemaphore;
+        private readonly Func<ILibraryManager> _libraryManager;
 
         public ImageProcessor(ILogger logger,
             IServerApplicationPaths appPaths,
             IFileSystem fileSystem,
             IJsonSerializer jsonSerializer,
             IImageEncoder imageEncoder,
-            int maxConcurrentImageProcesses)
+            int maxConcurrentImageProcesses, Func<ILibraryManager> libraryManager)
         {
             _logger = logger;
             _fileSystem = fileSystem;
             _jsonSerializer = jsonSerializer;
             _imageEncoder = imageEncoder;
+            _libraryManager = libraryManager;
             _appPaths = appPaths;
 
             ImageEnhancers = new List<IImageEnhancer>();
@@ -158,7 +161,14 @@ namespace Emby.Drawing
                 throw new ArgumentNullException("options");
             }
 
-            var originalImagePath = options.Image.Path;
+            var originalImage = options.Image;
+
+            if (!originalImage.IsLocalFile)
+            {
+                originalImage = await _libraryManager().ConvertImageToLocal(options.Item, originalImage, options.ImageIndex).ConfigureAwait(false);
+            }
+
+            var originalImagePath = originalImage.Path;
 
             if (options.HasDefaultOptions(originalImagePath) && options.Enhancers.Count == 0 && !options.CropWhiteSpace)
             {
@@ -166,7 +176,7 @@ namespace Emby.Drawing
                 return originalImagePath;
             }
 
-            var dateModified = options.Image.DateModified;
+            var dateModified = originalImage.DateModified;
 
             if (options.CropWhiteSpace)
             {
@@ -181,7 +191,7 @@ namespace Emby.Drawing
                 var tuple = await GetEnhancedImage(new ItemImageInfo
                 {
                     DateModified = dateModified,
-                    Type = options.Image.Type,
+                    Type = originalImage.Type,
                     Path = originalImagePath
 
                 }, options.Item, options.ImageIndex, options.Enhancers).ConfigureAwait(false);
@@ -360,19 +370,14 @@ namespace Emby.Drawing
             return GetCachePath(ResizedImageCachePath, filename, "." + format.ToString().ToLower());
         }
 
-        /// <summary>
-        /// Gets the size of the image.
-        /// </summary>
-        /// <param name="path">The path.</param>
-        /// <returns>ImageSize.</returns>
-        public ImageSize GetImageSize(string path)
+        public ImageSize GetImageSize(ItemImageInfo info)
         {
-            return GetImageSize(path, _fileSystem.GetLastWriteTimeUtc(path), false);
+            return GetImageSize(info.Path, info.DateModified, false);
         }
 
-        public ImageSize GetImageSize(ItemImageInfo info)
+        public ImageSize GetImageSize(string path)
         {
-            return GetImageSize(info.Path, info.DateModified, false);
+            return GetImageSize(path, _fileSystem.GetLastWriteTimeUtc(path), false);
         }
 
         /// <summary>
@@ -800,7 +805,6 @@ namespace Emby.Drawing
 
                     return false;
                 }
-
             });
         }
 

+ 6 - 3
MediaBrowser.Api/Images/ImageService.cs

@@ -319,10 +319,13 @@ namespace MediaBrowser.Api.Images
 
                 try
                 {
-                    var size = _imageProcessor.GetImageSize(info);
+                    if (info.IsLocalFile)
+                    {
+                        var size = _imageProcessor.GetImageSize(info);
 
-                    width = Convert.ToInt32(size.Width);
-                    height = Convert.ToInt32(size.Height);
+                        width = Convert.ToInt32(size.Width);
+                        height = Convert.ToInt32(size.Height);
+                    }
                 }
                 catch
                 {

+ 16 - 2
MediaBrowser.Api/Social/SharingService.cs

@@ -124,7 +124,7 @@ namespace MediaBrowser.Api.Social
             Task.WaitAll(task);
         }
 
-        public object Get(GetShareImage request)
+        public async Task<object> Get(GetShareImage request)
         {
             var share = _sharingManager.GetShareInfo(request.Id);
 
@@ -143,7 +143,21 @@ namespace MediaBrowser.Api.Social
 
             if (image != null)
             {
-                return ToStaticFileResult(image.Path);
+                if (image.IsLocalFile)
+                {
+                    return ToStaticFileResult(image.Path);
+                }
+
+                try
+                {
+                    // Don't fail the request over this
+                    var updatedImage = await _libraryManager.ConvertImageToLocal(item, image, 0).ConfigureAwait(false);
+                    return ToStaticFileResult(updatedImage.Path);
+                }
+                catch
+                {
+                    
+                }
             }
 
             // Grab a dlna icon if nothing else is available

+ 5 - 5
MediaBrowser.Controller/Drawing/IImageProcessor.cs

@@ -28,17 +28,17 @@ namespace MediaBrowser.Controller.Drawing
         /// <summary>
         /// Gets the size of the image.
         /// </summary>
-        /// <param name="path">The path.</param>
+        /// <param name="info">The information.</param>
         /// <returns>ImageSize.</returns>
-        ImageSize GetImageSize(string path);
+        ImageSize GetImageSize(ItemImageInfo info);
 
         /// <summary>
         /// Gets the size of the image.
         /// </summary>
-        /// <param name="info">The information.</param>
+        /// <param name="path">The path.</param>
         /// <returns>ImageSize.</returns>
-        ImageSize GetImageSize(ItemImageInfo info);
-
+        ImageSize GetImageSize(string path);
+        
         /// <summary>
         /// Adds the parts.
         /// </summary>

+ 51 - 17
MediaBrowser.Controller/Entities/BaseItem.cs

@@ -597,7 +597,7 @@ namespace MediaBrowser.Controller.Entities
         /// Gets or sets the custom rating.
         /// </summary>
         /// <value>The custom rating.</value>
-        [IgnoreDataMember]
+        //[IgnoreDataMember]
         public string CustomRating { get; set; }
 
         /// <summary>
@@ -628,7 +628,7 @@ namespace MediaBrowser.Controller.Entities
         /// Gets or sets the community rating.
         /// </summary>
         /// <value>The community rating.</value>
-        [IgnoreDataMember]
+        //[IgnoreDataMember]
         public float? CommunityRating { get; set; }
 
         /// <summary>
@@ -654,7 +654,7 @@ namespace MediaBrowser.Controller.Entities
         /// This could be episode number, album track number, etc.
         /// </summary>
         /// <value>The index number.</value>
-        [IgnoreDataMember]
+        //[IgnoreDataMember]
         public int? IndexNumber { get; set; }
 
         /// <summary>
@@ -1432,6 +1432,23 @@ namespace MediaBrowser.Controller.Entities
             return GetImageInfo(type, imageIndex) != null;
         }
 
+        public void SetImage(ItemImageInfo image, int index)
+        {
+            if (image.Type == ImageType.Chapter)
+            {
+                throw new ArgumentException("Cannot set chapter images using SetImagePath");
+            }
+
+            var existingImage = GetImageInfo(image.Type, index);
+
+            if (existingImage != null)
+            {
+                ImageInfos.Remove(existingImage);
+            }
+
+            ImageInfos.Add(image);
+        }
+
         public void SetImagePath(ImageType type, int index, FileSystemMetadata file)
         {
             if (type == ImageType.Chapter)
@@ -1473,18 +1490,21 @@ namespace MediaBrowser.Controller.Entities
             // Remove it from the item
             RemoveImage(info);
 
-            // Delete the source file
-            var currentFile = new FileInfo(info.Path);
-
-            // Deletion will fail if the file is hidden so remove the attribute first
-            if (currentFile.Exists)
+            if (info.IsLocalFile)
             {
-                if ((currentFile.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
+                // Delete the source file
+                var currentFile = new FileInfo(info.Path);
+
+                // Deletion will fail if the file is hidden so remove the attribute first
+                if (currentFile.Exists)
                 {
-                    currentFile.Attributes &= ~FileAttributes.Hidden;
-                }
+                    if ((currentFile.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
+                    {
+                        currentFile.Attributes &= ~FileAttributes.Hidden;
+                    }
 
-                FileSystem.DeleteFile(currentFile.FullName);
+                    FileSystem.DeleteFile(currentFile.FullName);
+                }
             }
 
             return UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None);
@@ -1505,11 +1525,16 @@ namespace MediaBrowser.Controller.Entities
         /// </summary>
         public bool ValidateImages(IDirectoryService directoryService)
         {
-            var allDirectories = ImageInfos.Select(i => System.IO.Path.GetDirectoryName(i.Path)).Distinct(StringComparer.OrdinalIgnoreCase).ToList();
-            var allFiles = allDirectories.SelectMany(directoryService.GetFiles).Select(i => i.FullName).ToList();
+            var allFiles = ImageInfos
+                .Where(i => i.IsLocalFile)
+                .Select(i => System.IO.Path.GetDirectoryName(i.Path))
+                .Distinct(StringComparer.OrdinalIgnoreCase)
+                .SelectMany(directoryService.GetFiles)
+                .Select(i => i.FullName)
+                .ToList();
 
             var deletedImages = ImageInfos
-                .Where(image => !allFiles.Contains(image.Path, StringComparer.OrdinalIgnoreCase))
+                .Where(image => image.IsLocalFile && !allFiles.Contains(image.Path, StringComparer.OrdinalIgnoreCase))
                 .ToList();
 
             if (deletedImages.Count > 0)
@@ -1619,7 +1644,10 @@ namespace MediaBrowser.Controller.Entities
                 }
                 else
                 {
-                    existing.DateModified = FileSystem.GetLastWriteTimeUtc(newImage);
+                    if (existing.IsLocalFile)
+                    {
+                        existing.DateModified = FileSystem.GetLastWriteTimeUtc(newImage);
+                    }
                 }
             }
 
@@ -1628,7 +1656,7 @@ namespace MediaBrowser.Controller.Entities
                 var newImagePaths = images.Select(i => i.FullName).ToList();
 
                 var deleted = existingImages
-					.Where(i => !newImagePaths.Contains(i.Path, StringComparer.OrdinalIgnoreCase) && !FileSystem.FileExists(i.Path))
+					.Where(i => i.IsLocalFile && !newImagePaths.Contains(i.Path, StringComparer.OrdinalIgnoreCase) && !FileSystem.FileExists(i.Path))
                     .ToList();
 
                 ImageInfos = ImageInfos.Except(deleted).ToList();
@@ -1679,6 +1707,12 @@ namespace MediaBrowser.Controller.Entities
                 return Task.FromResult(true);
             }
 
+            if (!info1.IsLocalFile || !info2.IsLocalFile)
+            {
+                // TODO: Not supported  yet
+                return Task.FromResult(true);
+            }
+
             var path1 = info1.Path;
             var path2 = info2.Path;
 

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

@@ -2,9 +2,11 @@
 using MediaBrowser.Model.Entities;
 using System.Collections.Generic;
 using System.IO;
+using System.Threading;
 using System.Threading.Tasks;
 using CommonIO;
 using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Library;
 
 namespace MediaBrowser.Controller.Entities
 {
@@ -191,6 +193,21 @@ namespace MediaBrowser.Controller.Entities
         /// </summary>
         /// <param name="image">The image.</param>
         void RemoveImage(ItemImageInfo image);
+
+        /// <summary>
+        /// Updates to repository.
+        /// </summary>
+        /// <param name="updateReason">The update reason.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        Task UpdateToRepository(ItemUpdateType updateReason, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Sets the image.
+        /// </summary>
+        /// <param name="image">The image.</param>
+        /// <param name="index">The index.</param>
+        void SetImage(ItemImageInfo image, int index);
     }
 
     public static class HasImagesExtensions

+ 0 - 8
MediaBrowser.Controller/Entities/IHasMetadata.cs

@@ -36,14 +36,6 @@ namespace MediaBrowser.Controller.Entities
         /// <value>The date last refreshed.</value>
         DateTime DateLastRefreshed { get; set; }
         
-        /// <summary>
-        /// Updates to repository.
-        /// </summary>
-        /// <param name="updateReason">The update reason.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
-        Task UpdateToRepository(ItemUpdateType updateReason, CancellationToken cancellationToken);
-
         /// <summary>
         /// This is called before any metadata refresh and returns true or false indicating if changes were made
         /// </summary>

+ 7 - 0
MediaBrowser.Controller/Entities/ItemImageInfo.cs

@@ -29,6 +29,13 @@ namespace MediaBrowser.Controller.Entities
         {
             get
             {
+                if (Path != null)
+                {
+                    if (Path.StartsWith("http", StringComparison.OrdinalIgnoreCase))
+                    {
+                        return false;
+                    }
+                }
                 return true;
             }
         }

+ 9 - 0
MediaBrowser.Controller/Library/ILibraryManager.cs

@@ -534,5 +534,14 @@ namespace MediaBrowser.Controller.Library
         /// <param name="to">To.</param>
         /// <returns>System.String.</returns>
         string SubstitutePath(string path, string from, string to);
+
+        /// <summary>
+        /// Converts the image to local.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <param name="image">The image.</param>
+        /// <param name="imageIndex">Index of the image.</param>
+        /// <returns>Task.</returns>
+        Task<ItemImageInfo> ConvertImageToLocal(IHasImages item, ItemImageInfo image, int imageIndex);
     }
 }

+ 3 - 2
MediaBrowser.Controller/LiveTv/ITunerHost.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Model.Dto;
+using System;
+using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.LiveTv;
 using System.Collections.Generic;
 using System.Threading;
@@ -37,7 +38,7 @@ namespace MediaBrowser.Controller.LiveTv
         /// <param name="streamId">The stream identifier.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task&lt;MediaSourceInfo&gt;.</returns>
-        Task<MediaSourceInfo> GetChannelStream(string channelId, string streamId, CancellationToken cancellationToken);
+        Task<Tuple<MediaSourceInfo,SemaphoreSlim>> GetChannelStream(string channelId, string streamId, CancellationToken cancellationToken);
         /// <summary>
         /// Gets the channel stream media sources.
         /// </summary>

+ 2 - 0
MediaBrowser.Controller/Providers/DynamicImageResponse.cs

@@ -1,12 +1,14 @@
 using System;
 using System.IO;
 using MediaBrowser.Model.Drawing;
+using MediaBrowser.Model.MediaInfo;
 
 namespace MediaBrowser.Controller.Providers
 {
     public class DynamicImageResponse
     {
         public string Path { get; set; }
+        public MediaProtocol Protocol { get; set; }
         public Stream Stream { get; set; }
         public ImageFormat Format { get; set; }
         public bool HasImage { get; set; }

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

@@ -226,11 +226,15 @@ namespace MediaBrowser.Model.Configuration
 
         public bool EnableDateLastRefresh { get; set; }
 
+        public string[] Migrations { get; set; }
+
         /// <summary>
         /// Initializes a new instance of the <see cref="ServerConfiguration" /> class.
         /// </summary>
         public ServerConfiguration()
         {
+            Migrations = new string[] {};
+
             ImageSavingConvention = ImageSavingConvention.Compatible;
             PublicPort = 8096;
             PublicHttpsPort = 8920;

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

@@ -136,7 +136,7 @@ namespace MediaBrowser.Providers.Manager
 
             source = memoryStream;
 
-            var currentPath = GetCurrentImagePath(item, type, index);
+            var currentImage = GetCurrentImage(item, type, index);
 
             using (source)
             {
@@ -160,8 +160,10 @@ namespace MediaBrowser.Providers.Manager
             SetImagePath(item, type, imageIndex, paths[0]);
 
             // Delete the current path
-            if (!string.IsNullOrEmpty(currentPath) && !paths.Contains(currentPath, StringComparer.OrdinalIgnoreCase))
+            if (currentImage != null && currentImage.IsLocalFile && !paths.Contains(currentImage.Path, StringComparer.OrdinalIgnoreCase))
             {
+                var currentPath = currentImage.Path;
+
                 _libraryMonitor.ReportFileSystemChangeBeginning(currentPath);
 
                 try
@@ -301,9 +303,9 @@ namespace MediaBrowser.Providers.Manager
         /// or
         /// imageIndex
         /// </exception>
-        private string GetCurrentImagePath(IHasImages item, ImageType type, int imageIndex)
+        private ItemImageInfo GetCurrentImage(IHasImages item, ImageType type, int imageIndex)
         {
-            return item.GetImagePath(type, imageIndex);
+            return item.GetImageInfo(type, imageIndex);
         }
 
         /// <summary>

+ 20 - 6
MediaBrowser.Providers/Manager/ItemImageProvider.cs

@@ -17,6 +17,7 @@ using System.Net;
 using System.Threading;
 using System.Threading.Tasks;
 using CommonIO;
+using MediaBrowser.Model.MediaInfo;
 
 namespace MediaBrowser.Providers.Manager
 {
@@ -138,11 +139,24 @@ namespace MediaBrowser.Providers.Manager
                         {
                             if (!string.IsNullOrEmpty(response.Path))
                             {
-                                var mimeType = MimeTypes.GetMimeType(response.Path);
-
-                                var stream = _fileSystem.GetFileStream(response.Path, FileMode.Open, FileAccess.Read, FileShare.Read, true);
-
-                                await _providerManager.SaveImage(item, stream, mimeType, imageType, null, response.InternalCacheKey, cancellationToken).ConfigureAwait(false);
+                                if (response.Protocol == MediaProtocol.Http)
+                                {
+                                    _logger.Debug("Setting image url into item {0}", item.Id);
+                                    item.SetImage(new ItemImageInfo
+                                    {
+                                        Path = response.Path,
+                                        Type = imageType
+
+                                    }, 0);
+                                }
+                                else
+                                {
+                                    var mimeType = MimeTypes.GetMimeType(response.Path);
+
+                                    var stream = _fileSystem.GetFileStream(response.Path, FileMode.Open, FileAccess.Read, FileShare.Read, true);
+
+                                    await _providerManager.SaveImage(item, stream, mimeType, imageType, null, response.InternalCacheKey, cancellationToken).ConfigureAwait(false);
+                                }
                             }
                             else
                             {
@@ -391,7 +405,7 @@ namespace MediaBrowser.Providers.Manager
                 else
                 {
                     var existing = item.GetImageInfo(type, 0);
-					if (existing != null && !_fileSystem.FileExists(existing.Path))
+                    if (existing != null && !_fileSystem.FileExists(existing.Path))
                     {
                         item.RemoveImage(existing);
                         changed = true;

+ 6 - 4
MediaBrowser.Providers/Music/AlbumImageFromSongProvider.cs

@@ -22,13 +22,15 @@ namespace MediaBrowser.Providers.Music
 
             var image = album.GetRecursiveChildren()
                 .OfType<Audio>()
-                .Select(i => i.GetImagePath(type))
-                .FirstOrDefault(i => !string.IsNullOrEmpty(i));
+                .Select(i => i.GetImageInfo(type, 0))
+                .FirstOrDefault(i => i != null && i.IsLocalFile);
+
+            var imagePath = image == null ? null : image.Path;
 
             return Task.FromResult(new DynamicImageResponse
             {
-                Path = image,
-                HasImage = !string.IsNullOrEmpty(image)
+                Path = imagePath,
+                HasImage = !string.IsNullOrEmpty(imagePath)
             });
         }
 

+ 4 - 18
MediaBrowser.Server.Implementations/Channels/ChannelItemImageProvider.cs

@@ -8,6 +8,7 @@ using System;
 using System.Collections.Generic;
 using System.Threading;
 using System.Threading.Tasks;
+using MediaBrowser.Model.MediaInfo;
 
 namespace MediaBrowser.Server.Implementations.Channels
 {
@@ -35,24 +36,9 @@ namespace MediaBrowser.Server.Implementations.Channels
 
             if (!string.IsNullOrEmpty(channelItem.ExternalImagePath))
             {
-                var options = new HttpRequestOptions
-                {
-                    CancellationToken = cancellationToken,
-                    Url = channelItem.ExternalImagePath
-                };
-
-                var response = await _httpClient.GetResponse(options).ConfigureAwait(false);
-
-                if (response.ContentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase))
-                {
-                    imageResponse.HasImage = true;
-                    imageResponse.Stream = response.Content;
-                    imageResponse.SetFormatFromMimeType(response.ContentType);
-                }
-                else
-                {
-                    _logger.Error("Provider did not return an image content type.");
-                }
+                imageResponse.Path = channelItem.ExternalImagePath;
+                imageResponse.Protocol = MediaProtocol.Http;
+                imageResponse.HasImage = true;
             }
 
             return imageResponse;

+ 1 - 1
MediaBrowser.Server.Implementations/Dto/DtoService.cs

@@ -1743,7 +1743,7 @@ namespace MediaBrowser.Server.Implementations.Dto
         {
             var imageInfo = item.GetImageInfo(ImageType.Primary, 0);
 
-            if (imageInfo == null)
+            if (imageInfo == null || !imageInfo.IsLocalFile)
             {
                 return;
             }

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

@@ -2354,5 +2354,17 @@ namespace MediaBrowser.Server.Implementations.Library
 
             return ItemRepository.UpdatePeople(item.Id, people);
         }
+
+        private readonly SemaphoreSlim _dynamicImageResourcePool = new SemaphoreSlim(1,1);
+        public async Task<ItemImageInfo> ConvertImageToLocal(IHasImages item, ItemImageInfo image, int imageIndex)
+        {
+            _logger.Debug("ConvertImageToLocal item {0}", item.Id);
+
+            await _providerManagerFactory().SaveImage(item, image.Path, _dynamicImageResourcePool, image.Type, imageIndex, CancellationToken.None).ConfigureAwait(false);
+
+            await item.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None).ConfigureAwait(false);
+
+            return item.GetImageInfo(image.Type, imageIndex);
+        }
     }
 }

+ 64 - 25
MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs

@@ -487,6 +487,29 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
         {
             _logger.Info("Streaming Channel " + channelId);
 
+            foreach (var hostInstance in _liveTvManager.TunerHosts)
+            {
+                try
+                {
+                    var result = await hostInstance.GetChannelStream(channelId, streamId, cancellationToken).ConfigureAwait(false);
+
+                    result.Item2.Release();
+
+                    return result.Item1;
+                }
+                catch (Exception e)
+                {
+                    _logger.ErrorException("Error getting channel stream", e);
+                }
+            }
+
+            throw new ApplicationException("Tuner not found.");
+        }
+
+        private async Task<Tuple<MediaSourceInfo, SemaphoreSlim>> GetChannelStreamInternal(string channelId, string streamId, CancellationToken cancellationToken)
+        {
+            _logger.Info("Streaming Channel " + channelId);
+
             foreach (var hostInstance in _liveTvManager.TunerHosts)
             {
                 try
@@ -653,40 +676,56 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
 
             try
             {
-                var mediaStreamInfo = await GetChannelStream(timer.ChannelId, null, CancellationToken.None);
-
-                // HDHR doesn't seem to release the tuner right away after first probing with ffmpeg
-                await Task.Delay(3000, cancellationToken).ConfigureAwait(false);
+                var result = await GetChannelStreamInternal(timer.ChannelId, null, CancellationToken.None);
+                var mediaStreamInfo = result.Item1;
+                var isResourceOpen = true;
 
-                var duration = recordingEndDate - DateTime.UtcNow;
-
-                HttpRequestOptions httpRequestOptions = new HttpRequestOptions()
+                // Unfortunately due to the semaphore we have to have a nested try/finally
+                try
                 {
-                    Url = mediaStreamInfo.Path
-                };
+                    // HDHR doesn't seem to release the tuner right away after first probing with ffmpeg
+                    await Task.Delay(3000, cancellationToken).ConfigureAwait(false);
+
+                    var duration = recordingEndDate - DateTime.UtcNow;
 
-                recording.Path = recordPath;
-                recording.Status = RecordingStatus.InProgress;
-                recording.DateLastUpdated = DateTime.UtcNow;
-                _recordingProvider.Update(recording);
+                    HttpRequestOptions httpRequestOptions = new HttpRequestOptions()
+                    {
+                        Url = mediaStreamInfo.Path
+                    };
+
+                    recording.Path = recordPath;
+                    recording.Status = RecordingStatus.InProgress;
+                    recording.DateLastUpdated = DateTime.UtcNow;
+                    _recordingProvider.Update(recording);
+
+                    _logger.Info("Beginning recording.");
+
+                    httpRequestOptions.BufferContent = false;
+                    var durationToken = new CancellationTokenSource(duration);
+                    var linkedToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
+                    httpRequestOptions.CancellationToken = linkedToken;
+                    _logger.Info("Writing file to path: " + recordPath);
+                    using (var response = await _httpClient.SendAsync(httpRequestOptions, "GET"))
+                    {
+                        using (var output = _fileSystem.GetFileStream(recordPath, FileMode.Create, FileAccess.Write, FileShare.Read))
+                        {
+                            result.Item2.Release();
+                            isResourceOpen = false;
 
-                _logger.Info("Beginning recording.");
+                            await response.Content.CopyToAsync(output, StreamDefaults.DefaultCopyToBufferSize, linkedToken);
+                        }
+                    }
 
-                httpRequestOptions.BufferContent = false;
-                var durationToken = new CancellationTokenSource(duration);
-                var linkedToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
-                httpRequestOptions.CancellationToken = linkedToken;
-                _logger.Info("Writing file to path: " + recordPath);
-                using (var response = await _httpClient.SendAsync(httpRequestOptions, "GET"))
+                    recording.Status = RecordingStatus.Completed;
+                    _logger.Info("Recording completed");
+                }
+                finally
                 {
-                    using (var output = _fileSystem.GetFileStream(recordPath, FileMode.Create, FileAccess.Write, FileShare.Read))
+                    if (isResourceOpen)
                     {
-                        await response.Content.CopyToAsync(output, StreamDefaults.DefaultCopyToBufferSize, linkedToken);
+                        result.Item2.Release();
                     }
                 }
-
-                recording.Status = RecordingStatus.Completed;
-                _logger.Info("Recording completed");
             }
             catch (OperationCanceledException)
             {

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

@@ -1446,7 +1446,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                 {
                     dto.ChannelName = channel.Name;
 
-                    if (!string.IsNullOrEmpty(channel.PrimaryImagePath))
+                    if (channel.HasImage(ImageType.Primary))
                     {
                         dto.ChannelPrimaryImageTag = _tvDtoService.GetImageTag(channel);
                     }
@@ -1512,7 +1512,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             {
                 dto.ChannelName = channel.Name;
 
-                if (!string.IsNullOrEmpty(channel.PrimaryImagePath))
+                if (channel.HasImage(ImageType.Primary))
                 {
                     dto.ChannelPrimaryImageTag = _tvDtoService.GetImageTag(channel);
                 }

+ 4 - 18
MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs

@@ -9,6 +9,7 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
+using MediaBrowser.Model.MediaInfo;
 
 namespace MediaBrowser.Server.Implementations.LiveTv
 {
@@ -40,24 +41,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             {
                 if (liveTvItem.ExternalImagePath.StartsWith("http", StringComparison.OrdinalIgnoreCase))
                 {
-                    var options = new HttpRequestOptions
-                    {
-                        CancellationToken = cancellationToken,
-                        Url = liveTvItem.ExternalImagePath
-                    };
-
-                    var response = await _httpClient.GetResponse(options).ConfigureAwait(false);
-
-                    if (response.ContentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase))
-                    {
-                        imageResponse.HasImage = true;
-                        imageResponse.Stream = response.Content;
-                        imageResponse.SetFormatFromMimeType(response.ContentType);
-                    }
-                    else
-                    {
-                        _logger.Error("Provider did not return an image content type.");
-                    }
+                    imageResponse.Path = liveTvItem.ExternalImagePath;
+                    imageResponse.Protocol = MediaProtocol.Http;
+                    imageResponse.HasImage = true;
                 }
                 else
                 {

+ 1 - 1
MediaBrowser.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs

@@ -9,7 +9,7 @@ using System.Threading.Tasks;
 
 namespace MediaBrowser.Server.Implementations.LiveTv
 {
-    class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask, IHasKey
+    public class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask, IHasKey
     {
         private readonly ILiveTvManager _liveTvManager;
         private readonly IConfigurationManager _config;

+ 39 - 5
MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs

@@ -141,7 +141,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
 
         protected abstract Task<MediaSourceInfo> GetChannelStream(TunerHostInfo tuner, string channelId, string streamId, CancellationToken cancellationToken);
 
-        public async Task<MediaSourceInfo> GetChannelStream(string channelId, string streamId, CancellationToken cancellationToken)
+        public async Task<Tuple<MediaSourceInfo, SemaphoreSlim>> GetChannelStream(string channelId, string streamId, CancellationToken cancellationToken)
         {
             if (IsValidChannelId(channelId))
             {
@@ -173,9 +173,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
                     try
                     {
                         var stream = await GetChannelStream(host, channelId, streamId, cancellationToken).ConfigureAwait(false);
-                        
-                        await AddMediaInfo(stream, false, cancellationToken).ConfigureAwait(false);
-                        return stream;
+                        var resourcePool = GetLock(host.Url);
+
+                        await AddMediaInfo(stream, false, resourcePool, cancellationToken).ConfigureAwait(false);
+                        return new Tuple<MediaSourceInfo, SemaphoreSlim>(stream, resourcePool);
                     }
                     catch (Exception ex)
                     {
@@ -187,7 +188,40 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
             throw new LiveTvConflictException();
         }
 
-        private async Task AddMediaInfo(MediaSourceInfo mediaSource, bool isAudio, CancellationToken cancellationToken)
+        /// <summary>
+        /// The _semaphoreLocks
+        /// </summary>
+        private readonly ConcurrentDictionary<string, SemaphoreSlim> _semaphoreLocks = new ConcurrentDictionary<string, SemaphoreSlim>(StringComparer.OrdinalIgnoreCase);
+        /// <summary>
+        /// Gets the lock.
+        /// </summary>
+        /// <param name="url">The filename.</param>
+        /// <returns>System.Object.</returns>
+        private SemaphoreSlim GetLock(string url)
+        {
+            return _semaphoreLocks.GetOrAdd(url, key => new SemaphoreSlim(1, 1));
+        }
+
+        private async Task AddMediaInfo(MediaSourceInfo mediaSource, bool isAudio, SemaphoreSlim resourcePool, CancellationToken cancellationToken)
+        {
+            await resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
+
+            try
+            {
+                await AddMediaInfoInternal(mediaSource, isAudio, cancellationToken).ConfigureAwait(false);
+
+                // Leave the resource locked. it will be released upstream
+            }
+            catch (Exception)
+            {
+                // Release the resource if there's some kind of failure.
+                resourcePool.Release();
+
+                throw;
+            }
+        }
+
+        private async Task AddMediaInfoInternal(MediaSourceInfo mediaSource, bool isAudio, CancellationToken cancellationToken)
         {
             var originalRuntime = mediaSource.RunTimeTicks;
 

+ 2 - 3
MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs

@@ -1,5 +1,4 @@
-using MediaBrowser.Common.IO;
-using MediaBrowser.Common.Progress;
+using MediaBrowser.Common.Progress;
 using MediaBrowser.Common.ScheduledTasks;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
@@ -17,7 +16,7 @@ using MediaBrowser.Controller.Entities.Audio;
 
 namespace MediaBrowser.Server.Implementations.Persistence
 {
-    class CleanDatabaseScheduledTask : IScheduledTask
+    public class CleanDatabaseScheduledTask : IScheduledTask
     {
         private readonly ILibraryManager _libraryManager;
         private readonly IItemRepository _itemRepo;

+ 16 - 1
MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs

@@ -125,7 +125,22 @@ namespace MediaBrowser.Server.Implementations.Photos
         protected virtual IEnumerable<string> GetStripCollageImagePaths(IHasImages primaryItem, IEnumerable<BaseItem> items)
         {
             return items
-                .Select(i => i.GetImagePath(ImageType.Primary) ?? i.GetImagePath(ImageType.Thumb))
+                .Select(i =>
+                {
+                    var image = i.GetImageInfo(ImageType.Primary, 0);
+
+                    if (image != null && image.IsLocalFile)
+                    {
+                        return image.Path;
+                    }
+                    image = i.GetImageInfo(ImageType.Thumb, 0);
+
+                    if (image != null && image.IsLocalFile)
+                    {
+                        return image.Path;
+                    }
+                    return null;
+                })
                 .Where(i => !string.IsNullOrWhiteSpace(i));
         }
 

+ 9 - 6
MediaBrowser.Server.Startup.Common/ApplicationHost.cs

@@ -333,18 +333,18 @@ namespace MediaBrowser.Server.Startup.Common
             });
 
             LogManager.RemoveConsoleOutput();
+
+            PerformPostInitMigrations();
         }
 
-        public override async Task Init(IProgress<double> progress)
+        public override Task Init(IProgress<double> progress)
         {
             HttpPort = ServerConfigurationManager.Configuration.HttpServerPortNumber;
             HttpsPort = ServerConfigurationManager.Configuration.HttpsPortNumber;
 
             PerformPreInitMigrations();
 
-            await base.Init(progress).ConfigureAwait(false);
-
-            PerformPostInitMigrations();
+            return base.Init(progress);
         }
 
         private void PerformPreInitMigrations()
@@ -362,7 +362,10 @@ namespace MediaBrowser.Server.Startup.Common
 
         private void PerformPostInitMigrations()
         {
-            var migrations = new List<IVersionMigration>();
+            var migrations = new List<IVersionMigration>
+            {
+                new Release5767(ServerConfigurationManager, TaskManager)
+            };
 
             foreach (var task in migrations)
             {
@@ -563,7 +566,7 @@ namespace MediaBrowser.Server.Startup.Common
                 int.TryParse(_startupOptions.GetOption("-imagethreads"), NumberStyles.Any, CultureInfo.InvariantCulture, out maxConcurrentImageProcesses);
             }
 
-            return new ImageProcessor(LogManager.GetLogger("ImageProcessor"), ServerConfigurationManager.ApplicationPaths, FileSystemManager, JsonSerializer, GetImageEncoder(), maxConcurrentImageProcesses);
+            return new ImageProcessor(LogManager.GetLogger("ImageProcessor"), ServerConfigurationManager.ApplicationPaths, FileSystemManager, JsonSerializer, GetImageEncoder(), maxConcurrentImageProcesses, () => LibraryManager);
         }
 
         private IImageEncoder GetImageEncoder()

+ 1 - 0
MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj

@@ -72,6 +72,7 @@
     <Compile Include="INativeApp.cs" />
     <Compile Include="MbLinkShortcutHandler.cs" />
     <Compile Include="Migrations\IVersionMigration.cs" />
+    <Compile Include="Migrations\Release5767.cs" />
     <Compile Include="Migrations\RenameXmlOptions.cs" />
     <Compile Include="NativeEnvironment.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />

+ 47 - 0
MediaBrowser.Server.Startup.Common/Migrations/Release5767.cs

@@ -0,0 +1,47 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using MediaBrowser.Common.ScheduledTasks;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Server.Implementations.LiveTv;
+using MediaBrowser.Server.Implementations.Persistence;
+using MediaBrowser.Server.Implementations.ScheduledTasks;
+
+namespace MediaBrowser.Server.Startup.Common.Migrations
+{
+    public class Release5767 : IVersionMigration
+    {
+        private readonly IServerConfigurationManager _config;
+        private readonly ITaskManager _taskManager;
+
+        public Release5767(IServerConfigurationManager config, ITaskManager taskManager)
+        {
+            _config = config;
+            _taskManager = taskManager;
+        }
+
+        public void Run()
+        {
+            var name = "5767";
+
+            if (_config.Configuration.Migrations.Contains(name, StringComparer.OrdinalIgnoreCase))
+            {
+                return;
+            }
+
+            Task.Run(async () =>
+            {
+                await Task.Delay(3000).ConfigureAwait(false);
+
+                _taskManager.QueueScheduledTask<RefreshChannelsScheduledTask>();
+                _taskManager.QueueScheduledTask<CleanDatabaseScheduledTask>();
+                _taskManager.QueueScheduledTask<RefreshMediaLibraryTask>();
+            });
+
+            var list = _config.Configuration.Migrations.ToList();
+            list.Add(name);
+            _config.Configuration.Migrations = list.ToArray();
+            _config.SaveConfiguration();
+        }
+    }
+}

+ 6 - 5
MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs

@@ -884,11 +884,11 @@ namespace MediaBrowser.XbmcMetadata.Savers
         {
             writer.WriteStartElement("art");
 
-            var poster = item.PrimaryImagePath;
+            var image = item.GetImageInfo(ImageType.Primary, 0);
 
-            if (!string.IsNullOrEmpty(poster))
+            if (image != null && image.IsLocalFile)
             {
-                writer.WriteElementString("poster", GetPathToSave(item.PrimaryImagePath, libraryManager, config));
+                writer.WriteElementString("poster", GetPathToSave(image.Path, libraryManager, config));
             }
 
             foreach (var backdrop in item.GetImages(ImageType.Backdrop))
@@ -985,10 +985,11 @@ namespace MediaBrowser.XbmcMetadata.Savers
                 try
                 {
                     var personEntity = libraryManager.GetPerson(person.Name);
+                    var image = personEntity.GetImageInfo(ImageType.Primary, 0);
 
-                    if (!string.IsNullOrEmpty(personEntity.PrimaryImagePath))
+                    if (image != null && image.IsLocalFile)
                     {
-                        writer.WriteElementString("thumb", GetPathToSave(personEntity.PrimaryImagePath, libraryManager, config));
+                        writer.WriteElementString("thumb", GetPathToSave(image.Path, libraryManager, config));
                     }
                 }
                 catch (Exception)