optimize image processor when gdi can be skipped

This commit is contained in:
Luke Pulverenti 2013-11-07 10:57:12 -05:00
parent d609d0edec
commit 2fc662c9e9
7 changed files with 153 additions and 70 deletions

View File

@ -1,4 +1,5 @@
 using System.Globalization;
namespace MediaBrowser.Model.Drawing namespace MediaBrowser.Model.Drawing
{ {
/// <summary> /// <summary>
@ -131,20 +132,77 @@ namespace MediaBrowser.Model.Drawing
/// </summary> /// </summary>
public struct ImageSize public struct ImageSize
{ {
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
private double _height;
private double _width;
/// <summary> /// <summary>
/// Gets or sets the height. /// Gets or sets the height.
/// </summary> /// </summary>
/// <value>The height.</value> /// <value>The height.</value>
public double Height { get; set; } public double Height
{
get
{
return _height;
}
set
{
_height = value;
}
}
/// <summary> /// <summary>
/// Gets or sets the width. /// Gets or sets the width.
/// </summary> /// </summary>
/// <value>The width.</value> /// <value>The width.</value>
public double Width { get; set; } public double Width
{
get { return _width; }
set { _width = value; }
}
public bool Equals(ImageSize size) public bool Equals(ImageSize size)
{ {
return Width.Equals(size.Width) && Height.Equals(size.Height); return Width.Equals(size.Width) && Height.Equals(size.Height);
} }
public override string ToString()
{
return string.Format("{0}-{1}", Width, Height);
}
public ImageSize(string value)
{
_width = 0;
_height = 0;
ParseValue(value);
}
private void ParseValue(string value)
{
if (!string.IsNullOrEmpty(value))
{
var parts = value.Split('-');
if (parts.Length == 2)
{
double val;
if (double.TryParse(parts[0], NumberStyles.Any, UsCulture, out val))
{
_width = val;
}
if (double.TryParse(parts[1], NumberStyles.Any, UsCulture, out val))
{
_height = val;
}
}
}
}
} }
} }

View File

@ -822,13 +822,18 @@ namespace MediaBrowser.Providers.Movies
// genres // genres
// Movies get this from imdb // Movies get this from imdb
if (movieData.genres != null && !movie.LockedFields.Contains(MetadataFields.Genres) && movie is BoxSet) if (movieData.genres != null && !movie.LockedFields.Contains(MetadataFields.Genres))
{ {
movie.Genres.Clear(); // Only grab them if a boxset or there are no genres.
// For movies and trailers we'll use imdb via omdb
foreach (var genre in movieData.genres.Select(g => g.name)) if (movie is BoxSet || movie.Genres.Count == 0)
{ {
movie.AddGenre(genre); movie.Genres.Clear();
foreach (var genre in movieData.genres.Select(g => g.name))
{
movie.AddGenre(genre);
}
} }
} }

View File

@ -70,9 +70,12 @@ namespace MediaBrowser.Providers.Music
/// <returns>Task{System.Boolean}.</returns> /// <returns>Task{System.Boolean}.</returns>
public override async Task<bool> FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) public override async Task<bool> FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken)
{ {
var images = await _providerManager.GetAvailableRemoteImages(item, cancellationToken, ManualLastFmImageProvider.ProviderName).ConfigureAwait(false); if (!item.HasImage(ImageType.Primary))
{
var images = await _providerManager.GetAvailableRemoteImages(item, cancellationToken, ManualLastFmImageProvider.ProviderName).ConfigureAwait(false);
await DownloadImages(item, images.ToList(), cancellationToken).ConfigureAwait(false); await DownloadImages(item, images.ToList(), cancellationToken).ConfigureAwait(false);
}
SetLastRefreshed(item, DateTime.UtcNow); SetLastRefreshed(item, DateTime.UtcNow);

View File

@ -7,6 +7,7 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
@ -24,7 +25,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
/// <summary> /// <summary>
/// Class ImageProcessor /// Class ImageProcessor
/// </summary> /// </summary>
public class ImageProcessor : IImageProcessor public class ImageProcessor : IImageProcessor, IDisposable
{ {
/// <summary> /// <summary>
/// The us culture /// The us culture
@ -34,7 +35,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
/// <summary> /// <summary>
/// The _cached imaged sizes /// The _cached imaged sizes
/// </summary> /// </summary>
private readonly ConcurrentDictionary<string, ImageSize> _cachedImagedSizes = new ConcurrentDictionary<string, ImageSize>(); private readonly ConcurrentDictionary<Guid, ImageSize> _cachedImagedSizes;
/// <summary> /// <summary>
/// Gets the list of currently registered image processors /// Gets the list of currently registered image processors
@ -49,21 +50,41 @@ namespace MediaBrowser.Server.Implementations.Drawing
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly IJsonSerializer _jsonSerializer;
private readonly IServerApplicationPaths _appPaths;
private readonly string _imageSizeCachePath; private readonly string _imageSizeCachePath;
private readonly string _croppedWhitespaceImageCachePath; private readonly string _croppedWhitespaceImageCachePath;
private readonly string _enhancedImageCachePath; private readonly string _enhancedImageCachePath;
private readonly string _resizedImageCachePath; private readonly string _resizedImageCachePath;
public ImageProcessor(ILogger logger, IServerApplicationPaths appPaths, IFileSystem fileSystem) public ImageProcessor(ILogger logger, IServerApplicationPaths appPaths, IFileSystem fileSystem, IJsonSerializer jsonSerializer)
{ {
_logger = logger; _logger = logger;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_jsonSerializer = jsonSerializer;
_appPaths = appPaths;
_imageSizeCachePath = Path.Combine(appPaths.ImageCachePath, "image-sizes"); _imageSizeCachePath = Path.Combine(appPaths.ImageCachePath, "image-sizes");
_croppedWhitespaceImageCachePath = Path.Combine(appPaths.ImageCachePath, "cropped-images"); _croppedWhitespaceImageCachePath = Path.Combine(appPaths.ImageCachePath, "cropped-images");
_enhancedImageCachePath = Path.Combine(appPaths.ImageCachePath, "enhanced-images"); _enhancedImageCachePath = Path.Combine(appPaths.ImageCachePath, "enhanced-images");
_resizedImageCachePath = Path.Combine(appPaths.ImageCachePath, "resized-images"); _resizedImageCachePath = Path.Combine(appPaths.ImageCachePath, "resized-images");
_saveImageSizeTimer = new Timer(SaveImageSizeCallback, null, Timeout.Infinite, Timeout.Infinite);
Dictionary<Guid, ImageSize> sizeDictionary;
try
{
sizeDictionary = jsonSerializer.DeserializeFromFile<Dictionary<Guid, ImageSize>>(ImageSizeFile);
}
catch (IOException)
{
// No biggie
sizeDictionary = new Dictionary<Guid, ImageSize>();
}
_cachedImagedSizes = new ConcurrentDictionary<Guid, ImageSize>(sizeDictionary);
} }
public void AddParts(IEnumerable<IImageEnhancer> enhancers) public void AddParts(IEnumerable<IImageEnhancer> enhancers)
@ -485,11 +506,13 @@ namespace MediaBrowser.Server.Implementations.Drawing
ImageSize size; ImageSize size;
if (!_cachedImagedSizes.TryGetValue(name, out size)) var cacheHash = name.GetMD5();
{
size = GetImageSizeInternal(name, path);
_cachedImagedSizes.AddOrUpdate(name, size, (keyName, oldValue) => size); if (!_cachedImagedSizes.TryGetValue(cacheHash, out size))
{
size = GetImageSizeInternal(path);
_cachedImagedSizes.AddOrUpdate(cacheHash, size, (keyName, oldValue) => size);
} }
return size; return size;
@ -498,62 +521,47 @@ namespace MediaBrowser.Server.Implementations.Drawing
/// <summary> /// <summary>
/// Gets the image size internal. /// Gets the image size internal.
/// </summary> /// </summary>
/// <param name="cacheKey">The cache key.</param>
/// <param name="path">The path.</param> /// <param name="path">The path.</param>
/// <returns>ImageSize.</returns> /// <returns>ImageSize.</returns>
private ImageSize GetImageSizeInternal(string cacheKey, string path) private ImageSize GetImageSizeInternal(string path)
{ {
// Now check the file system cache var size = ImageHeader.GetDimensions(path, _logger, _fileSystem);
var fullCachePath = GetCachePath(_imageSizeCachePath, cacheKey, ".txt");
try StartSaveImageSizeTimer();
{
var result = File.ReadAllText(fullCachePath).Split('|');
return new ImageSize return new ImageSize { Width = size.Width, Height = size.Height };
{ }
Width = double.Parse(result[0], UsCulture),
Height = double.Parse(result[1], UsCulture)
};
}
catch (IOException)
{
// Cache file doesn't exist or is currently being written to
}
var syncLock = GetObjectLock(fullCachePath); private readonly Timer _saveImageSizeTimer;
private const int SaveImageSizeTimeout = 5000;
private readonly object _saveImageSizeLock = new object();
private void StartSaveImageSizeTimer()
{
_saveImageSizeTimer.Change(SaveImageSizeTimeout, Timeout.Infinite);
}
lock (syncLock) private void SaveImageSizeCallback(object state)
{
lock (_saveImageSizeLock)
{ {
try try
{ {
var result = File.ReadAllText(fullCachePath).Split('|'); var path = ImageSizeFile;
Directory.CreateDirectory(Path.GetDirectoryName(path));
return new ImageSize _jsonSerializer.SerializeToFile(_cachedImagedSizes, path);
{
Width = double.Parse(result[0], UsCulture),
Height = double.Parse(result[1], UsCulture)
};
} }
catch (FileNotFoundException) catch (Exception ex)
{ {
// Cache file doesn't exist no biggie _logger.ErrorException("Error saving image size file", ex);
}
catch (DirectoryNotFoundException)
{
// Cache file doesn't exist no biggie
} }
}
}
var size = ImageHeader.GetDimensions(path, _logger, _fileSystem); private string ImageSizeFile
{
var parentPath = Path.GetDirectoryName(fullCachePath); get
{
Directory.CreateDirectory(parentPath); return Path.Combine(_appPaths.DataPath, "imagesizes.json");
// Update the file system cache
File.WriteAllText(fullCachePath, size.Width.ToString(UsCulture) + @"|" + size.Height.ToString(UsCulture));
return new ImageSize { Width = size.Width, Height = size.Height };
} }
} }
@ -882,5 +890,10 @@ namespace MediaBrowser.Server.Implementations.Drawing
}).ToList(); }).ToList();
} }
public void Dispose()
{
_saveImageSizeTimer.Dispose();
}
} }
} }

View File

@ -363,6 +363,7 @@ namespace MediaBrowser.Server.Implementations.IO
{ {
if (string.Equals(i, e.FullPath, StringComparison.OrdinalIgnoreCase)) if (string.Equals(i, e.FullPath, StringComparison.OrdinalIgnoreCase))
{ {
Logger.Debug("Watcher ignoring change to {0}", e.FullPath);
return true; return true;
} }
@ -370,6 +371,7 @@ namespace MediaBrowser.Server.Implementations.IO
var parent = Path.GetDirectoryName(i); var parent = Path.GetDirectoryName(i);
if (string.Equals(parent, e.FullPath, StringComparison.OrdinalIgnoreCase)) if (string.Equals(parent, e.FullPath, StringComparison.OrdinalIgnoreCase))
{ {
Logger.Debug("Watcher ignoring change to {0}", e.FullPath);
return true; return true;
} }
@ -379,10 +381,18 @@ namespace MediaBrowser.Server.Implementations.IO
parent = Path.GetDirectoryName(i); parent = Path.GetDirectoryName(i);
if (string.Equals(parent, e.FullPath, StringComparison.OrdinalIgnoreCase)) if (string.Equals(parent, e.FullPath, StringComparison.OrdinalIgnoreCase))
{ {
Logger.Debug("Watcher ignoring change to {0}", e.FullPath);
return true; return true;
} }
} }
if (i.StartsWith(e.FullPath, StringComparison.OrdinalIgnoreCase) ||
e.FullPath.StartsWith(i, StringComparison.OrdinalIgnoreCase))
{
Logger.Debug("Watcher ignoring change to {0}", e.FullPath);
return true;
}
return false; return false;
})) }))
@ -390,12 +400,6 @@ namespace MediaBrowser.Server.Implementations.IO
return; return;
} }
if (tempIgnorePaths.Contains(e.FullPath, StringComparer.OrdinalIgnoreCase))
{
Logger.Debug("Watcher requested to ignore change to " + e.FullPath);
return;
}
Logger.Info("Watcher sees change of type " + e.ChangeType + " to " + e.FullPath); Logger.Info("Watcher sees change of type " + e.ChangeType + " to " + e.FullPath);
//Since we're watching created, deleted and renamed we always want the parent of the item to be the affected path //Since we're watching created, deleted and renamed we always want the parent of the item to be the affected path

View File

@ -1,5 +1,4 @@
using System.Collections.Generic; using MediaBrowser.Common.IO;
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
@ -8,6 +7,7 @@ using MediaBrowser.Controller.IO;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using System; using System;
using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;

View File

@ -288,7 +288,7 @@ namespace MediaBrowser.ServerApplication
LocalizationManager = new LocalizationManager(ServerConfigurationManager, FileSystemManager); LocalizationManager = new LocalizationManager(ServerConfigurationManager, FileSystemManager);
RegisterSingleInstance(LocalizationManager); RegisterSingleInstance(LocalizationManager);
ImageProcessor = new ImageProcessor(Logger, ServerConfigurationManager.ApplicationPaths, FileSystemManager); ImageProcessor = new ImageProcessor(Logger, ServerConfigurationManager.ApplicationPaths, FileSystemManager, JsonSerializer);
RegisterSingleInstance(ImageProcessor); RegisterSingleInstance(ImageProcessor);
DtoService = new DtoService(Logger, LibraryManager, UserManager, UserDataManager, ItemRepository, ImageProcessor); DtoService = new DtoService(Logger, LibraryManager, UserManager, UserDataManager, ItemRepository, ImageProcessor);