mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-04-20 09:04:42 +01:00
Moved discovery of loggers and weather providers to MEF. Also added support for third-party image processors, also discovered through MEF.
This commit is contained in:
parent
01a25c48a0
commit
8b7effd6ff
33
MediaBrowser.Controller/Drawing/BaseImageProcessor.cs
Normal file
33
MediaBrowser.Controller/Drawing/BaseImageProcessor.cs
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
81
MediaBrowser.Controller/Drawing/DrawingUtils.cs
Normal file
81
MediaBrowser.Controller/Drawing/DrawingUtils.cs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
148
MediaBrowser.Controller/Drawing/ImageProcessor.cs
Normal file
148
MediaBrowser.Controller/Drawing/ImageProcessor.cs
Normal file
@@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
@@ -47,6 +47,12 @@ namespace MediaBrowser.Controller
|
||||
get { return KernelContext.Server; }
|
||||
}
|
||||
|
||||
/// <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>
|
||||
@@ -71,6 +77,12 @@ namespace MediaBrowser.Controller
|
||||
/// </summary>
|
||||
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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
34
MediaBrowser.Controller/Weather/BaseWeatherProvider.cs
Normal file
34
MediaBrowser.Controller/Weather/BaseWeatherProvider.cs
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user