This commit is contained in:
Eric Reed 2015-10-16 18:36:41 -04:00
commit 8ef0596f0f
29 changed files with 382 additions and 154 deletions

View File

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

View File

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

View File

@ -124,7 +124,7 @@ namespace MediaBrowser.Api.Social
Task.WaitAll(task); Task.WaitAll(task);
} }
public object Get(GetShareImage request) public async Task<object> Get(GetShareImage request)
{ {
var share = _sharingManager.GetShareInfo(request.Id); var share = _sharingManager.GetShareInfo(request.Id);
@ -143,7 +143,21 @@ namespace MediaBrowser.Api.Social
if (image != null) 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 // Grab a dlna icon if nothing else is available

View File

@ -25,13 +25,6 @@ namespace MediaBrowser.Controller.Drawing
/// <value>The image enhancers.</value> /// <value>The image enhancers.</value>
IEnumerable<IImageEnhancer> ImageEnhancers { get; } IEnumerable<IImageEnhancer> ImageEnhancers { get; }
/// <summary>
/// Gets the size of the image.
/// </summary>
/// <param name="path">The path.</param>
/// <returns>ImageSize.</returns>
ImageSize GetImageSize(string path);
/// <summary> /// <summary>
/// Gets the size of the image. /// Gets the size of the image.
/// </summary> /// </summary>
@ -39,6 +32,13 @@ namespace MediaBrowser.Controller.Drawing
/// <returns>ImageSize.</returns> /// <returns>ImageSize.</returns>
ImageSize GetImageSize(ItemImageInfo info); ImageSize GetImageSize(ItemImageInfo info);
/// <summary>
/// Gets the size of the image.
/// </summary>
/// <param name="path">The path.</param>
/// <returns>ImageSize.</returns>
ImageSize GetImageSize(string path);
/// <summary> /// <summary>
/// Adds the parts. /// Adds the parts.
/// </summary> /// </summary>

View File

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

View File

@ -2,9 +2,11 @@
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using CommonIO; using CommonIO;
using MediaBrowser.Common.IO; using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Library;
namespace MediaBrowser.Controller.Entities namespace MediaBrowser.Controller.Entities
{ {
@ -191,6 +193,21 @@ namespace MediaBrowser.Controller.Entities
/// </summary> /// </summary>
/// <param name="image">The image.</param> /// <param name="image">The image.</param>
void RemoveImage(ItemImageInfo image); 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 public static class HasImagesExtensions

View File

@ -36,14 +36,6 @@ namespace MediaBrowser.Controller.Entities
/// <value>The date last refreshed.</value> /// <value>The date last refreshed.</value>
DateTime DateLastRefreshed { get; set; } 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> /// <summary>
/// This is called before any metadata refresh and returns true or false indicating if changes were made /// This is called before any metadata refresh and returns true or false indicating if changes were made
/// </summary> /// </summary>

View File

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

View File

@ -534,5 +534,14 @@ namespace MediaBrowser.Controller.Library
/// <param name="to">To.</param> /// <param name="to">To.</param>
/// <returns>System.String.</returns> /// <returns>System.String.</returns>
string SubstitutePath(string path, string from, string to); 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);
} }
} }

View File

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

View File

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

View File

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

View File

@ -136,7 +136,7 @@ namespace MediaBrowser.Providers.Manager
source = memoryStream; source = memoryStream;
var currentPath = GetCurrentImagePath(item, type, index); var currentImage = GetCurrentImage(item, type, index);
using (source) using (source)
{ {
@ -160,8 +160,10 @@ namespace MediaBrowser.Providers.Manager
SetImagePath(item, type, imageIndex, paths[0]); SetImagePath(item, type, imageIndex, paths[0]);
// Delete the current path // 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); _libraryMonitor.ReportFileSystemChangeBeginning(currentPath);
try try
@ -301,9 +303,9 @@ namespace MediaBrowser.Providers.Manager
/// or /// or
/// imageIndex /// imageIndex
/// </exception> /// </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> /// <summary>

View File

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

View File

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

View File

@ -8,6 +8,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Model.MediaInfo;
namespace MediaBrowser.Server.Implementations.Channels namespace MediaBrowser.Server.Implementations.Channels
{ {
@ -35,24 +36,9 @@ namespace MediaBrowser.Server.Implementations.Channels
if (!string.IsNullOrEmpty(channelItem.ExternalImagePath)) if (!string.IsNullOrEmpty(channelItem.ExternalImagePath))
{ {
var options = new HttpRequestOptions imageResponse.Path = channelItem.ExternalImagePath;
{ imageResponse.Protocol = MediaProtocol.Http;
CancellationToken = cancellationToken, imageResponse.HasImage = true;
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.");
}
} }
return imageResponse; return imageResponse;

View File

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

View File

@ -2354,5 +2354,17 @@ namespace MediaBrowser.Server.Implementations.Library
return ItemRepository.UpdatePeople(item.Id, people); 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);
}
} }
} }

View File

@ -487,6 +487,29 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{ {
_logger.Info("Streaming Channel " + channelId); _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) foreach (var hostInstance in _liveTvManager.TunerHosts)
{ {
try try
@ -653,40 +676,56 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
try try
{ {
var mediaStreamInfo = await GetChannelStream(timer.ChannelId, null, CancellationToken.None); var result = await GetChannelStreamInternal(timer.ChannelId, null, CancellationToken.None);
var mediaStreamInfo = result.Item1;
var isResourceOpen = true;
// HDHR doesn't seem to release the tuner right away after first probing with ffmpeg // Unfortunately due to the semaphore we have to have a nested try/finally
await Task.Delay(3000, cancellationToken).ConfigureAwait(false); try
var duration = recordingEndDate - DateTime.UtcNow;
HttpRequestOptions httpRequestOptions = new HttpRequestOptions()
{ {
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);
recording.Path = recordPath; var duration = recordingEndDate - DateTime.UtcNow;
recording.Status = RecordingStatus.InProgress;
recording.DateLastUpdated = DateTime.UtcNow;
_recordingProvider.Update(recording);
_logger.Info("Beginning recording."); HttpRequestOptions httpRequestOptions = new HttpRequestOptions()
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))
{ {
await response.Content.CopyToAsync(output, StreamDefaults.DefaultCopyToBufferSize, linkedToken); 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;
await response.Content.CopyToAsync(output, StreamDefaults.DefaultCopyToBufferSize, linkedToken);
}
}
recording.Status = RecordingStatus.Completed;
_logger.Info("Recording completed");
}
finally
{
if (isResourceOpen)
{
result.Item2.Release();
} }
} }
recording.Status = RecordingStatus.Completed;
_logger.Info("Recording completed");
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {

View File

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

View File

@ -9,6 +9,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Model.MediaInfo;
namespace MediaBrowser.Server.Implementations.LiveTv namespace MediaBrowser.Server.Implementations.LiveTv
{ {
@ -40,24 +41,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv
{ {
if (liveTvItem.ExternalImagePath.StartsWith("http", StringComparison.OrdinalIgnoreCase)) if (liveTvItem.ExternalImagePath.StartsWith("http", StringComparison.OrdinalIgnoreCase))
{ {
var options = new HttpRequestOptions imageResponse.Path = liveTvItem.ExternalImagePath;
{ imageResponse.Protocol = MediaProtocol.Http;
CancellationToken = cancellationToken, imageResponse.HasImage = true;
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.");
}
} }
else else
{ {

View File

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

View File

@ -141,7 +141,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
protected abstract Task<MediaSourceInfo> GetChannelStream(TunerHostInfo tuner, string channelId, string streamId, CancellationToken cancellationToken); 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)) if (IsValidChannelId(channelId))
{ {
@ -173,9 +173,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
try try
{ {
var stream = await GetChannelStream(host, channelId, streamId, cancellationToken).ConfigureAwait(false); var stream = await GetChannelStream(host, channelId, streamId, cancellationToken).ConfigureAwait(false);
var resourcePool = GetLock(host.Url);
await AddMediaInfo(stream, false, cancellationToken).ConfigureAwait(false);
return stream; await AddMediaInfo(stream, false, resourcePool, cancellationToken).ConfigureAwait(false);
return new Tuple<MediaSourceInfo, SemaphoreSlim>(stream, resourcePool);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -187,7 +188,40 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
throw new LiveTvConflictException(); 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; var originalRuntime = mediaSource.RunTimeTicks;

View File

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

View File

@ -125,7 +125,22 @@ namespace MediaBrowser.Server.Implementations.Photos
protected virtual IEnumerable<string> GetStripCollageImagePaths(IHasImages primaryItem, IEnumerable<BaseItem> items) protected virtual IEnumerable<string> GetStripCollageImagePaths(IHasImages primaryItem, IEnumerable<BaseItem> items)
{ {
return 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)); .Where(i => !string.IsNullOrWhiteSpace(i));
} }

View File

@ -333,18 +333,18 @@ namespace MediaBrowser.Server.Startup.Common
}); });
LogManager.RemoveConsoleOutput(); LogManager.RemoveConsoleOutput();
PerformPostInitMigrations();
} }
public override async Task Init(IProgress<double> progress) public override Task Init(IProgress<double> progress)
{ {
HttpPort = ServerConfigurationManager.Configuration.HttpServerPortNumber; HttpPort = ServerConfigurationManager.Configuration.HttpServerPortNumber;
HttpsPort = ServerConfigurationManager.Configuration.HttpsPortNumber; HttpsPort = ServerConfigurationManager.Configuration.HttpsPortNumber;
PerformPreInitMigrations(); PerformPreInitMigrations();
await base.Init(progress).ConfigureAwait(false); return base.Init(progress);
PerformPostInitMigrations();
} }
private void PerformPreInitMigrations() private void PerformPreInitMigrations()
@ -362,7 +362,10 @@ namespace MediaBrowser.Server.Startup.Common
private void PerformPostInitMigrations() private void PerformPostInitMigrations()
{ {
var migrations = new List<IVersionMigration>(); var migrations = new List<IVersionMigration>
{
new Release5767(ServerConfigurationManager, TaskManager)
};
foreach (var task in migrations) 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); 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() private IImageEncoder GetImageEncoder()

View File

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

View File

@ -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();
}
}
}

View File

@ -884,11 +884,11 @@ namespace MediaBrowser.XbmcMetadata.Savers
{ {
writer.WriteStartElement("art"); 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)) foreach (var backdrop in item.GetImages(ImageType.Backdrop))
@ -985,10 +985,11 @@ namespace MediaBrowser.XbmcMetadata.Savers
try try
{ {
var personEntity = libraryManager.GetPerson(person.Name); 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) catch (Exception)