diff options
Diffstat (limited to 'MediaBrowser.Controller')
| -rw-r--r-- | MediaBrowser.Controller/Drawing/BaseImageProcessor.cs | 33 | ||||
| -rw-r--r-- | MediaBrowser.Controller/Drawing/DrawingUtils.cs | 81 | ||||
| -rw-r--r-- | MediaBrowser.Controller/Drawing/ImageProcessor.cs | 148 | ||||
| -rw-r--r-- | MediaBrowser.Controller/Kernel.cs | 46 | ||||
| -rw-r--r-- | MediaBrowser.Controller/MediaBrowser.Controller.csproj | 7 | ||||
| -rw-r--r-- | MediaBrowser.Controller/Weather/BaseWeatherProvider.cs | 34 | ||||
| -rw-r--r-- | MediaBrowser.Controller/Weather/WeatherProvider.cs (renamed from MediaBrowser.Controller/Weather/WeatherClient.cs) | 26 |
7 files changed, 323 insertions, 52 deletions
diff --git a/MediaBrowser.Controller/Drawing/BaseImageProcessor.cs b/MediaBrowser.Controller/Drawing/BaseImageProcessor.cs new file mode 100644 index 000000000..a2b223a70 --- /dev/null +++ b/MediaBrowser.Controller/Drawing/BaseImageProcessor.cs @@ -0,0 +1,33 @@ +using MediaBrowser.Controller.Entities;
+using MediaBrowser.Model.Entities;
+using System.Drawing;
+
+namespace MediaBrowser.Controller.Drawing
+{
+ /// <summary>
+ /// Provides a base image processor class that plugins can use to process images as they are being writen to http responses
+ /// Since this is completely modular with MEF, a plugin only needs to have a subclass in their assembly with the following attribute on the class:
+ /// [Export(typeof(BaseImageProcessor))]
+ /// This will require a reference to System.ComponentModel.Composition
+ /// </summary>
+ public abstract class BaseImageProcessor
+ {
+ /// <summary>
+ /// Processes the primary image for a BaseEntity (Person, Studio, User, etc)
+ /// </summary>
+ /// <param name="bitmap">The bitmap holding the original image, after re-sizing</param>
+ /// <param name="graphics">The graphics surface on which the output is drawn</param>
+ /// <param name="entity">The entity that owns the image</param>
+ public abstract void ProcessImage(Bitmap bitmap, Graphics graphics, BaseEntity entity);
+
+ /// <summary>
+ /// Processes an image for a BaseItem
+ /// </summary>
+ /// <param name="bitmap">The bitmap holding the original image, after re-sizing</param>
+ /// <param name="graphics">The graphics surface on which the output is drawn</param>
+ /// <param name="entity">The entity that owns the image</param>
+ /// <param name="imageType">The image type</param>
+ /// <param name="imageIndex">The image index (currently only used with backdrops)</param>
+ public abstract void ProcessImage(Bitmap bitmap, Graphics graphics, BaseItem entity, ImageType imageType, int imageIndex);
+ }
+}
diff --git a/MediaBrowser.Controller/Drawing/DrawingUtils.cs b/MediaBrowser.Controller/Drawing/DrawingUtils.cs new file mode 100644 index 000000000..8e2f829b9 --- /dev/null +++ b/MediaBrowser.Controller/Drawing/DrawingUtils.cs @@ -0,0 +1,81 @@ +using System;
+using System.Drawing;
+
+namespace MediaBrowser.Controller.Drawing
+{
+ public static class DrawingUtils
+ {
+ /// <summary>
+ /// Resizes a set of dimensions
+ /// </summary>
+ public static Size Resize(int currentWidth, int currentHeight, int? width, int? height, int? maxWidth, int? maxHeight)
+ {
+ return Resize(new Size(currentWidth, currentHeight), width, height, maxWidth, maxHeight);
+ }
+
+ /// <summary>
+ /// Resizes a set of dimensions
+ /// </summary>
+ /// <param name="size">The original size object</param>
+ /// <param name="width">A new fixed width, if desired</param>
+ /// <param name="height">A new fixed neight, if desired</param>
+ /// <param name="maxWidth">A max fixed width, if desired</param>
+ /// <param name="maxHeight">A max fixed height, if desired</param>
+ /// <returns>A new size object</returns>
+ public static Size Resize(Size size, int? width, int? height, int? maxWidth, int? maxHeight)
+ {
+ decimal newWidth = size.Width;
+ decimal newHeight = size.Height;
+
+ if (width.HasValue && height.HasValue)
+ {
+ newWidth = width.Value;
+ newHeight = height.Value;
+ }
+
+ else if (height.HasValue)
+ {
+ newWidth = GetNewWidth(newHeight, newWidth, height.Value);
+ newHeight = height.Value;
+ }
+
+ else if (width.HasValue)
+ {
+ newHeight = GetNewHeight(newHeight, newWidth, width.Value);
+ newWidth = width.Value;
+ }
+
+ if (maxHeight.HasValue && maxHeight < newHeight)
+ {
+ newWidth = GetNewWidth(newHeight, newWidth, maxHeight.Value);
+ newHeight = maxHeight.Value;
+ }
+
+ if (maxWidth.HasValue && maxWidth < newWidth)
+ {
+ newHeight = GetNewHeight(newHeight, newWidth, maxWidth.Value);
+ newWidth = maxWidth.Value;
+ }
+
+ return new Size(Convert.ToInt32(newWidth), Convert.ToInt32(newHeight));
+ }
+
+ private static decimal GetNewWidth(decimal currentHeight, decimal currentWidth, int newHeight)
+ {
+ decimal scaleFactor = newHeight;
+ scaleFactor /= currentHeight;
+ scaleFactor *= currentWidth;
+
+ return scaleFactor;
+ }
+
+ private static decimal GetNewHeight(decimal currentHeight, decimal currentWidth, int newWidth)
+ {
+ decimal scaleFactor = newWidth;
+ scaleFactor /= currentWidth;
+ scaleFactor *= currentHeight;
+
+ return scaleFactor;
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/Drawing/ImageProcessor.cs b/MediaBrowser.Controller/Drawing/ImageProcessor.cs new file mode 100644 index 000000000..b7815750b --- /dev/null +++ b/MediaBrowser.Controller/Drawing/ImageProcessor.cs @@ -0,0 +1,148 @@ +using MediaBrowser.Controller.Entities;
+using MediaBrowser.Model.Entities;
+using System;
+using System.Drawing;
+using System.Drawing.Drawing2D;
+using System.Drawing.Imaging;
+using System.IO;
+using System.Linq;
+
+namespace MediaBrowser.Controller.Drawing
+{
+ public static class ImageProcessor
+ {
+ /// <summary>
+ /// Processes an image by resizing to target dimensions
+ /// </summary>
+ /// <param name="sourceImageStream">The stream containing the source image</param>
+ /// <param name="toStream">The stream to save the new image to</param>
+ /// <param name="width">Use if a fixed width is required. Aspect ratio will be preserved.</param>
+ /// <param name="height">Use if a fixed height is required. Aspect ratio will be preserved.</param>
+ /// <param name="maxWidth">Use if a max width is required. Aspect ratio will be preserved.</param>
+ /// <param name="maxHeight">Use if a max height is required. Aspect ratio will be preserved.</param>
+ /// <param name="quality">Quality level, from 0-100. Currently only applies to JPG. The default value should suffice.</param>
+ /// <param name="entity">The entity that owns the image</param>
+ /// <param name="imageType">The image type</param>
+ /// <param name="imageIndex">The image index (currently only used with backdrops)</param>
+ public static void ProcessImage(Stream sourceImageStream, Stream toStream, int? width, int? height, int? maxWidth, int? maxHeight, int? quality, BaseEntity entity, ImageType imageType, int imageIndex)
+ {
+ Image originalImage = Image.FromStream(sourceImageStream);
+
+ // Determine the output size based on incoming parameters
+ Size newSize = DrawingUtils.Resize(originalImage.Size, width, height, maxWidth, maxHeight);
+
+ Bitmap thumbnail;
+
+ // Graphics.FromImage will throw an exception if the PixelFormat is Indexed, so we need to handle that here
+ if (originalImage.PixelFormat.HasFlag(PixelFormat.Indexed))
+ {
+ thumbnail = new Bitmap(originalImage, newSize.Width, newSize.Height);
+ }
+ else
+ {
+ thumbnail = new Bitmap(newSize.Width, newSize.Height, originalImage.PixelFormat);
+ }
+
+ // Preserve the original resolution
+ thumbnail.SetResolution(originalImage.HorizontalResolution, originalImage.VerticalResolution);
+
+ Graphics thumbnailGraph = Graphics.FromImage(thumbnail);
+
+ thumbnailGraph.CompositingQuality = CompositingQuality.HighQuality;
+ thumbnailGraph.SmoothingMode = SmoothingMode.HighQuality;
+ thumbnailGraph.InterpolationMode = InterpolationMode.HighQualityBicubic;
+ thumbnailGraph.PixelOffsetMode = PixelOffsetMode.HighQuality;
+ thumbnailGraph.CompositingMode = CompositingMode.SourceOver;
+
+ thumbnailGraph.DrawImage(originalImage, 0, 0, newSize.Width, newSize.Height);
+
+ // Run Kernel image processors
+ if (Kernel.Instance.ImageProcessors.Any())
+ {
+ ExecuteAdditionalImageProcessors(thumbnail, thumbnailGraph, entity, imageType, imageIndex);
+ }
+
+ // Write to the output stream
+ SaveImage(originalImage.RawFormat, thumbnail, toStream, quality);
+
+ thumbnailGraph.Dispose();
+ thumbnail.Dispose();
+ originalImage.Dispose();
+ }
+
+ /// <summary>
+ /// Executes additional image processors that are registered with the Kernel
+ /// </summary>
+ /// <param name="bitmap">The bitmap holding the original image, after re-sizing</param>
+ /// <param name="graphics">The graphics surface on which the output is drawn</param>
+ /// <param name="entity">The entity that owns the image</param>
+ /// <param name="imageType">The image type</param>
+ /// <param name="imageIndex">The image index (currently only used with backdrops)</param>
+ private static void ExecuteAdditionalImageProcessors(Bitmap bitmap, Graphics graphics, BaseEntity entity, ImageType imageType, int imageIndex)
+ {
+ var baseItem = entity as BaseItem;
+
+ if (baseItem != null)
+ {
+ foreach (var processor in Kernel.Instance.ImageProcessors)
+ {
+ processor.ProcessImage(bitmap, graphics, baseItem, imageType, imageIndex);
+ }
+ }
+ else
+ {
+ foreach (var processor in Kernel.Instance.ImageProcessors)
+ {
+ processor.ProcessImage(bitmap, graphics, entity);
+ }
+ }
+ }
+
+ public static void SaveImage(ImageFormat originalImageRawFormat, Image newImage, Stream toStream, int? quality)
+ {
+ // Use special save methods for jpeg and png that will result in a much higher quality image
+ // All other formats use the generic Image.Save
+ if (ImageFormat.Jpeg.Equals(originalImageRawFormat))
+ {
+ SaveJpeg(newImage, toStream, quality);
+ }
+ else if (ImageFormat.Png.Equals(originalImageRawFormat))
+ {
+ newImage.Save(toStream, ImageFormat.Png);
+ }
+ else
+ {
+ newImage.Save(toStream, originalImageRawFormat);
+ }
+ }
+
+ public static void SaveJpeg(Image image, Stream target, int? quality)
+ {
+ if (!quality.HasValue)
+ {
+ quality = 90;
+ }
+
+ using (var encoderParameters = new EncoderParameters(1))
+ {
+ encoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, quality.Value);
+ image.Save(target, GetImageCodecInfo("image/jpeg"), encoderParameters);
+ }
+ }
+
+ public static ImageCodecInfo GetImageCodecInfo(string mimeType)
+ {
+ ImageCodecInfo[] info = ImageCodecInfo.GetImageEncoders();
+
+ for (int i = 0; i < info.Length; i++)
+ {
+ ImageCodecInfo ici = info[i];
+ if (ici.MimeType.Equals(mimeType, StringComparison.OrdinalIgnoreCase))
+ {
+ return ici;
+ }
+ }
+ return info[1];
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/Kernel.cs b/MediaBrowser.Controller/Kernel.cs index 682abb632..4c0dc6965 100644 --- a/MediaBrowser.Controller/Kernel.cs +++ b/MediaBrowser.Controller/Kernel.cs @@ -1,5 +1,6 @@ using MediaBrowser.Common.Kernel;
using MediaBrowser.Common.Logging;
+using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.IO;
@@ -27,7 +28,6 @@ namespace MediaBrowser.Controller public static Kernel Instance { get; private set; }
public ItemController ItemController { get; private set; }
- public WeatherClient WeatherClient { get; private set; }
public IEnumerable<User> Users { get; private set; }
public Folder RootFolder { get; private set; }
@@ -48,6 +48,12 @@ namespace MediaBrowser.Controller }
/// <summary>
+ /// Gets the list of currently registered weather prvoiders
+ /// </summary>
+ [ImportMany(typeof(BaseWeatherProvider))]
+ public IEnumerable<BaseWeatherProvider> WeatherProviders { get; private set; }
+
+ /// <summary>
/// Gets the list of currently registered metadata prvoiders
/// </summary>
[ImportMany(typeof(BaseMetadataProvider))]
@@ -72,6 +78,12 @@ namespace MediaBrowser.Controller internal IBaseItemResolver[] EntityResolvers { get; private set; }
/// <summary>
+ /// Gets the list of currently registered entity resolvers
+ /// </summary>
+ [ImportMany(typeof(BaseImageProcessor))]
+ internal IEnumerable<BaseImageProcessor> ImageProcessors { get; private set; }
+
+ /// <summary>
/// Creates a kernel based on a Data path, which is akin to our current programdata path
/// </summary>
public Kernel()
@@ -85,13 +97,15 @@ namespace MediaBrowser.Controller /// </summary>
protected override void InitializeInternal(IProgress<TaskProgress> progress)
{
+ base.InitializeInternal(progress);
+
ItemController = new ItemController();
DirectoryWatchers = new DirectoryWatchers();
ItemController.PreBeginResolvePath += ItemController_PreBeginResolvePath;
ItemController.BeginResolvePath += ItemController_BeginResolvePath;
- base.InitializeInternal(progress);
+ ExtractFFMpeg();
}
/// <summary>
@@ -101,14 +115,11 @@ namespace MediaBrowser.Controller {
await base.ReloadInternal(progress).ConfigureAwait(false);
- ReloadWeatherClient();
-
- ExtractFFMpeg();
-
ReportProgress(progress, "Loading Users");
ReloadUsers();
ReportProgress(progress, "Loading Media Library");
+
await ReloadRoot(allowInternetProviders: false).ConfigureAwait(false);
}
@@ -121,8 +132,6 @@ namespace MediaBrowser.Controller DirectoryWatchers.Stop();
- DisposeWeatherClient();
-
ItemController.PreBeginResolvePath -= ItemController_PreBeginResolvePath;
ItemController.BeginResolvePath -= ItemController_BeginResolvePath;
}
@@ -413,26 +422,5 @@ namespace MediaBrowser.Controller }
}
}
-
- /// <summary>
- /// Disposes the current WeatherClient
- /// </summary>
- private void DisposeWeatherClient()
- {
- if (WeatherClient != null)
- {
- WeatherClient.Dispose();
- }
- }
-
- /// <summary>
- /// Disposes the current WeatherClient and creates a new one
- /// </summary>
- private void ReloadWeatherClient()
- {
- DisposeWeatherClient();
-
- WeatherClient = new WeatherClient();
- }
}
}
diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 8d1f4965a..131825af3 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -36,6 +36,7 @@ <Reference Include="System" />
<Reference Include="System.ComponentModel.Composition" />
<Reference Include="System.Core" />
+ <Reference Include="System.Drawing" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Net.Http.WebRequest" />
<Reference Include="System.Reactive.Core, Version=2.0.20823.0, Culture=neutral, PublicKeyToken=f300afd708cefcd3, processorArchitecture=MSIL">
@@ -58,6 +59,9 @@ <Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
+ <Compile Include="Drawing\BaseImageProcessor.cs" />
+ <Compile Include="Drawing\DrawingUtils.cs" />
+ <Compile Include="Drawing\ImageProcessor.cs" />
<Compile Include="Entities\Audio.cs" />
<Compile Include="Entities\BaseEntity.cs" />
<Compile Include="Entities\BaseItem.cs" />
@@ -107,7 +111,8 @@ <Compile Include="Resolvers\BaseItemResolver.cs" />
<Compile Include="Resolvers\FolderResolver.cs" />
<Compile Include="Resolvers\VideoResolver.cs" />
- <Compile Include="Weather\WeatherClient.cs" />
+ <Compile Include="Weather\BaseWeatherProvider.cs" />
+ <Compile Include="Weather\WeatherProvider.cs" />
<Compile Include="Providers\BaseItemXmlParser.cs" />
<Compile Include="Xml\XmlExtensions.cs" />
</ItemGroup>
diff --git a/MediaBrowser.Controller/Weather/BaseWeatherProvider.cs b/MediaBrowser.Controller/Weather/BaseWeatherProvider.cs new file mode 100644 index 000000000..c3d436e66 --- /dev/null +++ b/MediaBrowser.Controller/Weather/BaseWeatherProvider.cs @@ -0,0 +1,34 @@ +using MediaBrowser.Common.Logging;
+using MediaBrowser.Model.Weather;
+using System;
+using System.Net;
+using System.Net.Cache;
+using System.Net.Http;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Controller.Weather
+{
+ public abstract class BaseWeatherProvider : IDisposable
+ {
+ protected HttpClient HttpClient { get; private set; }
+
+ protected BaseWeatherProvider()
+ {
+ var handler = new WebRequestHandler { };
+
+ handler.AutomaticDecompression = DecompressionMethods.Deflate;
+ handler.CachePolicy = new RequestCachePolicy(RequestCacheLevel.Revalidate);
+
+ HttpClient = new HttpClient(handler);
+ }
+
+ public virtual void Dispose()
+ {
+ Logger.LogInfo("Disposing " + GetType().Name);
+
+ HttpClient.Dispose();
+ }
+
+ public abstract Task<WeatherInfo> GetWeatherInfoAsync(string zipCode);
+ }
+}
diff --git a/MediaBrowser.Controller/Weather/WeatherClient.cs b/MediaBrowser.Controller/Weather/WeatherProvider.cs index 7226dccf0..0fc728879 100644 --- a/MediaBrowser.Controller/Weather/WeatherClient.cs +++ b/MediaBrowser.Controller/Weather/WeatherProvider.cs @@ -2,11 +2,9 @@ using MediaBrowser.Common.Serialization;
using MediaBrowser.Model.Weather;
using System;
+using System.ComponentModel.Composition;
using System.IO;
using System.Linq;
-using System.Net;
-using System.Net.Cache;
-using System.Net.Http;
using System.Threading.Tasks;
namespace MediaBrowser.Controller.Weather
@@ -15,21 +13,10 @@ namespace MediaBrowser.Controller.Weather /// Based on http://www.worldweatheronline.com/free-weather-feed.aspx
/// The classes in this file are a reproduction of the json output, which will then be converted to our weather model classes
/// </summary>
- public class WeatherClient : IDisposable
+ [Export(typeof(BaseWeatherProvider))]
+ public class WeatherProvider : BaseWeatherProvider
{
- private HttpClient HttpClient { get; set; }
-
- public WeatherClient()
- {
- var handler = new WebRequestHandler { };
-
- handler.AutomaticDecompression = DecompressionMethods.Deflate;
- handler.CachePolicy = new RequestCachePolicy(RequestCacheLevel.Revalidate);
-
- HttpClient = new HttpClient(handler);
- }
-
- public async Task<WeatherInfo> GetWeatherInfoAsync(string zipCode)
+ public override async Task<WeatherInfo> GetWeatherInfoAsync(string zipCode)
{
if (string.IsNullOrWhiteSpace(zipCode))
{
@@ -73,11 +60,6 @@ namespace MediaBrowser.Controller.Weather return info;
}
-
- public void Dispose()
- {
- HttpClient.Dispose();
- }
}
class WeatherResult
|
