mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-03-06 18:26:33 +00:00
Rename Emby.Drawing and move to src (#9054)
* Move Emby.Drawing to src * Rename Emby.Drawing -> Jellyfin.Drawing
This commit is contained in:
569
src/Jellyfin.Drawing/ImageProcessor.cs
Normal file
569
src/Jellyfin.Drawing/ImageProcessor.cs
Normal file
@@ -0,0 +1,569 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Mime;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Entities;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Model.Drawing;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Net;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Photo = MediaBrowser.Controller.Entities.Photo;
|
||||
|
||||
namespace Jellyfin.Drawing
|
||||
{
|
||||
/// <summary>
|
||||
/// Class ImageProcessor.
|
||||
/// </summary>
|
||||
public sealed class ImageProcessor : IImageProcessor, IDisposable
|
||||
{
|
||||
// Increment this when there's a change requiring caches to be invalidated
|
||||
private const char Version = '3';
|
||||
|
||||
private static readonly HashSet<string> _transparentImageTypes
|
||||
= new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".png", ".webp", ".gif" };
|
||||
|
||||
private readonly ILogger<ImageProcessor> _logger;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IServerApplicationPaths _appPaths;
|
||||
private readonly IImageEncoder _imageEncoder;
|
||||
private readonly IMediaEncoder _mediaEncoder;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ImageProcessor"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="appPaths">The server application paths.</param>
|
||||
/// <param name="fileSystem">The filesystem.</param>
|
||||
/// <param name="imageEncoder">The image encoder.</param>
|
||||
/// <param name="mediaEncoder">The media encoder.</param>
|
||||
public ImageProcessor(
|
||||
ILogger<ImageProcessor> logger,
|
||||
IServerApplicationPaths appPaths,
|
||||
IFileSystem fileSystem,
|
||||
IImageEncoder imageEncoder,
|
||||
IMediaEncoder mediaEncoder)
|
||||
{
|
||||
_logger = logger;
|
||||
_fileSystem = fileSystem;
|
||||
_imageEncoder = imageEncoder;
|
||||
_mediaEncoder = mediaEncoder;
|
||||
_appPaths = appPaths;
|
||||
}
|
||||
|
||||
private string ResizedImageCachePath => Path.Combine(_appPaths.ImageCachePath, "resized-images");
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyCollection<string> SupportedInputFormats =>
|
||||
new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"tiff",
|
||||
"tif",
|
||||
"jpeg",
|
||||
"jpg",
|
||||
"png",
|
||||
"aiff",
|
||||
"cr2",
|
||||
"crw",
|
||||
"nef",
|
||||
"orf",
|
||||
"pef",
|
||||
"arw",
|
||||
"webp",
|
||||
"gif",
|
||||
"bmp",
|
||||
"erf",
|
||||
"raf",
|
||||
"rw2",
|
||||
"nrw",
|
||||
"dng",
|
||||
"ico",
|
||||
"astc",
|
||||
"ktx",
|
||||
"pkm",
|
||||
"wbmp"
|
||||
};
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool SupportsImageCollageCreation => _imageEncoder.SupportsImageCollageCreation;
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task ProcessImage(ImageProcessingOptions options, Stream toStream)
|
||||
{
|
||||
var file = await ProcessImage(options).ConfigureAwait(false);
|
||||
using (var fileStream = AsyncFile.OpenRead(file.Path))
|
||||
{
|
||||
await fileStream.CopyToAsync(toStream).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyCollection<ImageFormat> GetSupportedImageOutputFormats()
|
||||
=> _imageEncoder.SupportedOutputFormats;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool SupportsTransparency(string path)
|
||||
=> _transparentImageTypes.Contains(Path.GetExtension(path));
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<(string Path, string? MimeType, DateTime DateModified)> ProcessImage(ImageProcessingOptions options)
|
||||
{
|
||||
ItemImageInfo originalImage = options.Image;
|
||||
BaseItem item = options.Item;
|
||||
|
||||
string originalImagePath = originalImage.Path;
|
||||
DateTime dateModified = originalImage.DateModified;
|
||||
ImageDimensions? originalImageSize = null;
|
||||
if (originalImage.Width > 0 && originalImage.Height > 0)
|
||||
{
|
||||
originalImageSize = new ImageDimensions(originalImage.Width, originalImage.Height);
|
||||
}
|
||||
|
||||
var mimeType = MimeTypes.GetMimeType(originalImagePath);
|
||||
if (!_imageEncoder.SupportsImageEncoding)
|
||||
{
|
||||
return (originalImagePath, mimeType, dateModified);
|
||||
}
|
||||
|
||||
var supportedImageInfo = await GetSupportedImage(originalImagePath, dateModified).ConfigureAwait(false);
|
||||
originalImagePath = supportedImageInfo.Path;
|
||||
|
||||
// Original file doesn't exist, or original file is gif.
|
||||
if (!File.Exists(originalImagePath) || string.Equals(mimeType, MediaTypeNames.Image.Gif, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return (originalImagePath, mimeType, dateModified);
|
||||
}
|
||||
|
||||
dateModified = supportedImageInfo.DateModified;
|
||||
bool requiresTransparency = _transparentImageTypes.Contains(Path.GetExtension(originalImagePath));
|
||||
|
||||
bool autoOrient = false;
|
||||
ImageOrientation? orientation = null;
|
||||
if (item is Photo photo)
|
||||
{
|
||||
if (photo.Orientation.HasValue)
|
||||
{
|
||||
if (photo.Orientation.Value != ImageOrientation.TopLeft)
|
||||
{
|
||||
autoOrient = true;
|
||||
orientation = photo.Orientation;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Orientation unknown, so do it
|
||||
autoOrient = true;
|
||||
orientation = photo.Orientation;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.HasDefaultOptions(originalImagePath, originalImageSize) && (!autoOrient || !options.RequiresAutoOrientation))
|
||||
{
|
||||
// Just spit out the original file if all the options are default
|
||||
return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
|
||||
}
|
||||
|
||||
int quality = options.Quality;
|
||||
|
||||
ImageFormat outputFormat = GetOutputFormat(options.SupportedOutputFormats, requiresTransparency);
|
||||
string cacheFilePath = GetCacheFilePath(
|
||||
originalImagePath,
|
||||
options.Width,
|
||||
options.Height,
|
||||
options.MaxWidth,
|
||||
options.MaxHeight,
|
||||
options.FillWidth,
|
||||
options.FillHeight,
|
||||
quality,
|
||||
dateModified,
|
||||
outputFormat,
|
||||
options.AddPlayedIndicator,
|
||||
options.PercentPlayed,
|
||||
options.UnplayedCount,
|
||||
options.Blur,
|
||||
options.BackgroundColor,
|
||||
options.ForegroundLayer);
|
||||
|
||||
try
|
||||
{
|
||||
if (!File.Exists(cacheFilePath))
|
||||
{
|
||||
string resultPath = _imageEncoder.EncodeImage(originalImagePath, dateModified, cacheFilePath, autoOrient, orientation, quality, options, outputFormat);
|
||||
|
||||
if (string.Equals(resultPath, originalImagePath, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
|
||||
}
|
||||
}
|
||||
|
||||
return (cacheFilePath, GetMimeType(outputFormat, cacheFilePath), _fileSystem.GetLastWriteTimeUtc(cacheFilePath));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// If it fails for whatever reason, return the original image
|
||||
_logger.LogError(ex, "Error encoding image");
|
||||
return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
|
||||
}
|
||||
}
|
||||
|
||||
private ImageFormat GetOutputFormat(IReadOnlyCollection<ImageFormat> clientSupportedFormats, bool requiresTransparency)
|
||||
{
|
||||
var serverFormats = GetSupportedImageOutputFormats();
|
||||
|
||||
// Client doesn't care about format, so start with webp if supported
|
||||
if (serverFormats.Contains(ImageFormat.Webp) && clientSupportedFormats.Contains(ImageFormat.Webp))
|
||||
{
|
||||
return ImageFormat.Webp;
|
||||
}
|
||||
|
||||
// If transparency is needed and webp isn't supported, than png is the only option
|
||||
if (requiresTransparency && clientSupportedFormats.Contains(ImageFormat.Png))
|
||||
{
|
||||
return ImageFormat.Png;
|
||||
}
|
||||
|
||||
foreach (var format in clientSupportedFormats)
|
||||
{
|
||||
if (serverFormats.Contains(format))
|
||||
{
|
||||
return format;
|
||||
}
|
||||
}
|
||||
|
||||
// We should never actually get here
|
||||
return ImageFormat.Jpg;
|
||||
}
|
||||
|
||||
private string GetMimeType(ImageFormat format, string path)
|
||||
=> format switch
|
||||
{
|
||||
ImageFormat.Bmp => MimeTypes.GetMimeType("i.bmp"),
|
||||
ImageFormat.Gif => MimeTypes.GetMimeType("i.gif"),
|
||||
ImageFormat.Jpg => MimeTypes.GetMimeType("i.jpg"),
|
||||
ImageFormat.Png => MimeTypes.GetMimeType("i.png"),
|
||||
ImageFormat.Webp => MimeTypes.GetMimeType("i.webp"),
|
||||
_ => MimeTypes.GetMimeType(path)
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Gets the cache file path based on a set of parameters.
|
||||
/// </summary>
|
||||
private string GetCacheFilePath(
|
||||
string originalPath,
|
||||
int? width,
|
||||
int? height,
|
||||
int? maxWidth,
|
||||
int? maxHeight,
|
||||
int? fillWidth,
|
||||
int? fillHeight,
|
||||
int quality,
|
||||
DateTime dateModified,
|
||||
ImageFormat format,
|
||||
bool addPlayedIndicator,
|
||||
double percentPlayed,
|
||||
int? unwatchedCount,
|
||||
int? blur,
|
||||
string backgroundColor,
|
||||
string foregroundLayer)
|
||||
{
|
||||
var filename = new StringBuilder(256);
|
||||
filename.Append(originalPath);
|
||||
|
||||
filename.Append(",quality=");
|
||||
filename.Append(quality);
|
||||
|
||||
filename.Append(",datemodified=");
|
||||
filename.Append(dateModified.Ticks);
|
||||
|
||||
filename.Append(",f=");
|
||||
filename.Append(format);
|
||||
|
||||
if (width.HasValue)
|
||||
{
|
||||
filename.Append(",width=");
|
||||
filename.Append(width.Value);
|
||||
}
|
||||
|
||||
if (height.HasValue)
|
||||
{
|
||||
filename.Append(",height=");
|
||||
filename.Append(height.Value);
|
||||
}
|
||||
|
||||
if (maxWidth.HasValue)
|
||||
{
|
||||
filename.Append(",maxwidth=");
|
||||
filename.Append(maxWidth.Value);
|
||||
}
|
||||
|
||||
if (maxHeight.HasValue)
|
||||
{
|
||||
filename.Append(",maxheight=");
|
||||
filename.Append(maxHeight.Value);
|
||||
}
|
||||
|
||||
if (fillWidth.HasValue)
|
||||
{
|
||||
filename.Append(",fillwidth=");
|
||||
filename.Append(fillWidth.Value);
|
||||
}
|
||||
|
||||
if (fillHeight.HasValue)
|
||||
{
|
||||
filename.Append(",fillheight=");
|
||||
filename.Append(fillHeight.Value);
|
||||
}
|
||||
|
||||
if (addPlayedIndicator)
|
||||
{
|
||||
filename.Append(",pl=true");
|
||||
}
|
||||
|
||||
if (percentPlayed > 0)
|
||||
{
|
||||
filename.Append(",p=");
|
||||
filename.Append(percentPlayed);
|
||||
}
|
||||
|
||||
if (unwatchedCount.HasValue)
|
||||
{
|
||||
filename.Append(",p=");
|
||||
filename.Append(unwatchedCount.Value);
|
||||
}
|
||||
|
||||
if (blur.HasValue)
|
||||
{
|
||||
filename.Append(",blur=");
|
||||
filename.Append(blur.Value);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(backgroundColor))
|
||||
{
|
||||
filename.Append(",b=");
|
||||
filename.Append(backgroundColor);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(foregroundLayer))
|
||||
{
|
||||
filename.Append(",fl=");
|
||||
filename.Append(foregroundLayer);
|
||||
}
|
||||
|
||||
filename.Append(",v=");
|
||||
filename.Append(Version);
|
||||
|
||||
return GetCachePath(ResizedImageCachePath, filename.ToString(), "." + format.ToString().ToLowerInvariant());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info)
|
||||
{
|
||||
int width = info.Width;
|
||||
int height = info.Height;
|
||||
|
||||
if (height > 0 && width > 0)
|
||||
{
|
||||
return new ImageDimensions(width, height);
|
||||
}
|
||||
|
||||
string path = info.Path;
|
||||
_logger.LogDebug("Getting image size for item {ItemType} {Path}", item.GetType().Name, path);
|
||||
|
||||
ImageDimensions size = GetImageDimensions(path);
|
||||
info.Width = size.Width;
|
||||
info.Height = size.Height;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ImageDimensions GetImageDimensions(string path)
|
||||
=> _imageEncoder.GetImageSize(path);
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetImageBlurHash(string path)
|
||||
{
|
||||
var size = GetImageDimensions(path);
|
||||
return GetImageBlurHash(path, size);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetImageBlurHash(string path, ImageDimensions imageDimensions)
|
||||
{
|
||||
if (imageDimensions.Width <= 0 || imageDimensions.Height <= 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
// We want tiles to be as close to square as possible, and to *mostly* keep under 16 tiles for performance.
|
||||
// One tile is (width / xComp) x (height / yComp) pixels, which means that ideally yComp = xComp * height / width.
|
||||
// See more at https://github.com/woltapp/blurhash/#how-do-i-pick-the-number-of-x-and-y-components
|
||||
float xCompF = MathF.Sqrt(16.0f * imageDimensions.Width / imageDimensions.Height);
|
||||
float yCompF = xCompF * imageDimensions.Height / imageDimensions.Width;
|
||||
|
||||
int xComp = Math.Min((int)xCompF + 1, 9);
|
||||
int yComp = Math.Min((int)yCompF + 1, 9);
|
||||
|
||||
return _imageEncoder.GetImageBlurHash(xComp, yComp, path);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetImageCacheTag(BaseItem item, ItemImageInfo image)
|
||||
=> (item.Path + image.DateModified.Ticks).GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetImageCacheTag(BaseItem item, ChapterInfo chapter)
|
||||
{
|
||||
return GetImageCacheTag(item, new ItemImageInfo
|
||||
{
|
||||
Path = chapter.ImagePath,
|
||||
Type = ImageType.Chapter,
|
||||
DateModified = chapter.ImageDateModified
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string? GetImageCacheTag(User user)
|
||||
{
|
||||
if (user.ProfileImage is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return (user.ProfileImage.Path + user.ProfileImage.LastModified.Ticks).GetMD5()
|
||||
.ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
private Task<(string Path, DateTime DateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified)
|
||||
{
|
||||
var inputFormat = Path.GetExtension(originalImagePath.AsSpan()).TrimStart('.').ToString();
|
||||
|
||||
// These are just jpg files renamed as tbn
|
||||
if (string.Equals(inputFormat, "tbn", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return Task.FromResult((originalImagePath, dateModified));
|
||||
}
|
||||
|
||||
// TODO _mediaEncoder.ConvertImage is not implemented
|
||||
// if (!_imageEncoder.SupportedInputFormats.Contains(inputFormat))
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// string filename = (originalImagePath + dateModified.Ticks.ToString(CultureInfo.InvariantCulture)).GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||
//
|
||||
// string cacheExtension = _mediaEncoder.SupportsEncoder("libwebp") ? ".webp" : ".png";
|
||||
// var outputPath = Path.Combine(_appPaths.ImageCachePath, "converted-images", filename + cacheExtension);
|
||||
//
|
||||
// var file = _fileSystem.GetFileInfo(outputPath);
|
||||
// if (!file.Exists)
|
||||
// {
|
||||
// await _mediaEncoder.ConvertImage(originalImagePath, outputPath).ConfigureAwait(false);
|
||||
// dateModified = _fileSystem.GetLastWriteTimeUtc(outputPath);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// dateModified = file.LastWriteTimeUtc;
|
||||
// }
|
||||
//
|
||||
// originalImagePath = outputPath;
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// _logger.LogError(ex, "Image conversion failed for {Path}", originalImagePath);
|
||||
// }
|
||||
// }
|
||||
|
||||
return Task.FromResult((originalImagePath, dateModified));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the cache path.
|
||||
/// </summary>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <param name="uniqueName">Name of the unique.</param>
|
||||
/// <param name="fileExtension">The file extension.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// path
|
||||
/// or
|
||||
/// uniqueName
|
||||
/// or
|
||||
/// fileExtension.
|
||||
/// </exception>
|
||||
public string GetCachePath(string path, string uniqueName, string fileExtension)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrEmpty(path);
|
||||
ArgumentException.ThrowIfNullOrEmpty(uniqueName);
|
||||
ArgumentException.ThrowIfNullOrEmpty(fileExtension);
|
||||
|
||||
var filename = uniqueName.GetMD5() + fileExtension;
|
||||
|
||||
return GetCachePath(path, filename);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the cache path.
|
||||
/// </summary>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <param name="filename">The filename.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// path
|
||||
/// or
|
||||
/// filename.
|
||||
/// </exception>
|
||||
public string GetCachePath(ReadOnlySpan<char> path, ReadOnlySpan<char> filename)
|
||||
{
|
||||
if (path.IsEmpty)
|
||||
{
|
||||
throw new ArgumentException("Path can't be empty.", nameof(path));
|
||||
}
|
||||
|
||||
if (filename.IsEmpty)
|
||||
{
|
||||
throw new ArgumentException("Filename can't be empty.", nameof(filename));
|
||||
}
|
||||
|
||||
var prefix = filename.Slice(0, 1);
|
||||
|
||||
return Path.Join(path, prefix, filename);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void CreateImageCollage(ImageCollageOptions options, string? libraryName)
|
||||
{
|
||||
_logger.LogInformation("Creating image collage and saving to {Path}", options.OutputPath);
|
||||
|
||||
_imageEncoder.CreateImageCollage(options, libraryName);
|
||||
|
||||
_logger.LogInformation("Completed creation of image collage and saved to {Path}", options.OutputPath);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_imageEncoder is IDisposable disposable)
|
||||
{
|
||||
disposable.Dispose();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
35
src/Jellyfin.Drawing/Jellyfin.Drawing.csproj
Normal file
35
src/Jellyfin.Drawing/Jellyfin.Drawing.csproj
Normal file
@@ -0,0 +1,35 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
||||
<PropertyGroup>
|
||||
<ProjectGuid>{08FFF49B-F175-4807-A2B5-73B0EBD9F716}</ProjectGuid>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
|
||||
<ProjectReference Include="..\..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
|
||||
<ProjectReference Include="..\..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\..\SharedVersion.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Code analysers-->
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
|
||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
58
src/Jellyfin.Drawing/NullImageEncoder.cs
Normal file
58
src/Jellyfin.Drawing/NullImageEncoder.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Model.Drawing;
|
||||
|
||||
namespace Jellyfin.Drawing
|
||||
{
|
||||
/// <summary>
|
||||
/// A fallback implementation of <see cref="IImageEncoder" />.
|
||||
/// </summary>
|
||||
public class NullImageEncoder : IImageEncoder
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyCollection<string> SupportedInputFormats
|
||||
=> new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "png", "jpeg", "jpg" };
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyCollection<ImageFormat> SupportedOutputFormats
|
||||
=> new HashSet<ImageFormat>() { ImageFormat.Jpg, ImageFormat.Png };
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => "Null Image Encoder";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool SupportsImageCollageCreation => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool SupportsImageEncoding => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ImageDimensions GetImageSize(string path)
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat outputFormat)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void CreateImageCollage(ImageCollageOptions options, string? libraryName)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void CreateSplashscreen(IReadOnlyList<string> posters, IReadOnlyList<string> backdrops)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetImageBlurHash(int xComp, int yComp, string path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
30
src/Jellyfin.Drawing/Properties/AssemblyInfo.cs
Normal file
30
src/Jellyfin.Drawing/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("Jellyfin.Drawing")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Jellyfin Project")]
|
||||
[assembly: AssemblyProduct("Jellyfin Server")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("87b6f14e-16d8-4a58-a553-fd9945e47458")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
Reference in New Issue
Block a user