Add BlurHash support to backend
This commit is contained in:
parent
2d2c1d9473
commit
b9fc0d2628
|
@ -313,6 +313,10 @@ namespace Emby.Drawing
|
||||||
public ImageDimensions GetImageDimensions(string path)
|
public ImageDimensions GetImageDimensions(string path)
|
||||||
=> _imageEncoder.GetImageSize(path);
|
=> _imageEncoder.GetImageSize(path);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string GetImageHash(string path)
|
||||||
|
=> _imageEncoder.GetImageHash(path);
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string GetImageCacheTag(BaseItem item, ItemImageInfo image)
|
public string GetImageCacheTag(BaseItem item, ItemImageInfo image)
|
||||||
=> (item.Path + image.DateModified.Ticks).GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
=> (item.Path + image.DateModified.Ticks).GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||||
|
|
|
@ -42,5 +42,11 @@ namespace Emby.Drawing
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string GetImageHash(string inputPath)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1144,12 +1144,18 @@ namespace Emby.Server.Implementations.Data
|
||||||
var delimeter = "*";
|
var delimeter = "*";
|
||||||
|
|
||||||
var path = image.Path;
|
var path = image.Path;
|
||||||
|
var hash = image.Hash;
|
||||||
|
|
||||||
if (path == null)
|
if (path == null)
|
||||||
{
|
{
|
||||||
path = string.Empty;
|
path = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hash == null)
|
||||||
|
{
|
||||||
|
hash = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
return GetPathToSave(path) +
|
return GetPathToSave(path) +
|
||||||
delimeter +
|
delimeter +
|
||||||
image.DateModified.Ticks.ToString(CultureInfo.InvariantCulture) +
|
image.DateModified.Ticks.ToString(CultureInfo.InvariantCulture) +
|
||||||
|
@ -1158,7 +1164,11 @@ namespace Emby.Server.Implementations.Data
|
||||||
delimeter +
|
delimeter +
|
||||||
image.Width.ToString(CultureInfo.InvariantCulture) +
|
image.Width.ToString(CultureInfo.InvariantCulture) +
|
||||||
delimeter +
|
delimeter +
|
||||||
image.Height.ToString(CultureInfo.InvariantCulture);
|
image.Height.ToString(CultureInfo.InvariantCulture) +
|
||||||
|
delimeter +
|
||||||
|
// Replace delimiters with other characters.
|
||||||
|
// This can be removed when we migrate to a proper DB.
|
||||||
|
hash.Replace('*', '/').Replace('|', '\\');
|
||||||
}
|
}
|
||||||
|
|
||||||
public ItemImageInfo ItemImageInfoFromValueString(string value)
|
public ItemImageInfo ItemImageInfoFromValueString(string value)
|
||||||
|
@ -1192,6 +1202,11 @@ namespace Emby.Server.Implementations.Data
|
||||||
image.Width = width;
|
image.Width = width;
|
||||||
image.Height = height;
|
image.Height = height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (parts.Length >= 6)
|
||||||
|
{
|
||||||
|
image.Hash = parts[5].Replace('/', '*').Replace('\\', '|');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return image;
|
return image;
|
||||||
|
|
|
@ -718,6 +718,7 @@ namespace Emby.Server.Implementations.Dto
|
||||||
if (options.EnableImages)
|
if (options.EnableImages)
|
||||||
{
|
{
|
||||||
dto.ImageTags = new Dictionary<ImageType, string>();
|
dto.ImageTags = new Dictionary<ImageType, string>();
|
||||||
|
dto.ImageHashes = new Dictionary<string, string>();
|
||||||
|
|
||||||
// Prevent implicitly captured closure
|
// Prevent implicitly captured closure
|
||||||
var currentItem = item;
|
var currentItem = item;
|
||||||
|
@ -732,6 +733,12 @@ namespace Emby.Server.Implementations.Dto
|
||||||
{
|
{
|
||||||
dto.ImageTags[image.Type] = tag;
|
dto.ImageTags[image.Type] = tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var hash = image.Hash;
|
||||||
|
if (hash != null && hash.Length > 0)
|
||||||
|
{
|
||||||
|
dto.ImageHashes[tag] = image.Hash;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Common.Progress;
|
using MediaBrowser.Common.Progress;
|
||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Controller;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
|
using MediaBrowser.Controller.Drawing;
|
||||||
using MediaBrowser.Controller.Dto;
|
using MediaBrowser.Controller.Dto;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Entities.Audio;
|
using MediaBrowser.Controller.Entities.Audio;
|
||||||
|
@ -35,6 +36,7 @@ using MediaBrowser.Controller.Resolvers;
|
||||||
using MediaBrowser.Controller.Sorting;
|
using MediaBrowser.Controller.Sorting;
|
||||||
using MediaBrowser.Model.Configuration;
|
using MediaBrowser.Model.Configuration;
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
|
using MediaBrowser.Model.Drawing;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
|
@ -109,6 +111,18 @@ namespace Emby.Server.Implementations.Library
|
||||||
/// <value>The comparers.</value>
|
/// <value>The comparers.</value>
|
||||||
private IBaseItemComparer[] Comparers { get; set; }
|
private IBaseItemComparer[] Comparers { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the active item repository
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The item repository.</value>
|
||||||
|
public IItemRepository ItemRepository { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the active image processor
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The image processor.</value>
|
||||||
|
public IImageProcessor ImageProcessor { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Occurs when [item added].
|
/// Occurs when [item added].
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -1817,7 +1831,19 @@ namespace Emby.Server.Implementations.Library
|
||||||
|
|
||||||
public void UpdateImages(BaseItem item)
|
public void UpdateImages(BaseItem item)
|
||||||
{
|
{
|
||||||
_itemRepository.SaveImages(item);
|
item.ImageInfos
|
||||||
|
.Where(i => (i.Width == 0 || i.Height == 0))
|
||||||
|
.ToList()
|
||||||
|
.ForEach(x =>
|
||||||
|
{
|
||||||
|
string blurhash = ImageProcessor.GetImageHash(x.Path);
|
||||||
|
ImageDimensions size = ImageProcessor.GetImageDimensions(item, x, true);
|
||||||
|
x.Width = size.Width;
|
||||||
|
x.Height = size.Height;
|
||||||
|
x.Hash = blurhash;
|
||||||
|
});
|
||||||
|
|
||||||
|
ItemRepository.SaveImages(item);
|
||||||
|
|
||||||
RegisterItem(item);
|
RegisterItem(item);
|
||||||
}
|
}
|
||||||
|
@ -1839,7 +1865,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
|
|
||||||
item.DateLastSaved = DateTime.UtcNow;
|
item.DateLastSaved = DateTime.UtcNow;
|
||||||
|
|
||||||
RegisterItem(item);
|
UpdateImages(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
_itemRepository.SaveItems(itemsList, cancellationToken);
|
_itemRepository.SaveItems(itemsList, cancellationToken);
|
||||||
|
|
|
@ -21,6 +21,8 @@
|
||||||
<PackageReference Include="SkiaSharp" Version="1.68.1" />
|
<PackageReference Include="SkiaSharp" Version="1.68.1" />
|
||||||
<PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="1.68.1" />
|
<PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="1.68.1" />
|
||||||
<PackageReference Include="Jellyfin.SkiaSharp.NativeAssets.LinuxArm" Version="1.68.1" />
|
<PackageReference Include="Jellyfin.SkiaSharp.NativeAssets.LinuxArm" Version="1.68.1" />
|
||||||
|
|
||||||
|
<PackageReference Include="Blurhash.Core" Version="*" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Blurhash.Core;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Controller.Drawing;
|
using MediaBrowser.Controller.Drawing;
|
||||||
using MediaBrowser.Controller.Extensions;
|
using MediaBrowser.Controller.Extensions;
|
||||||
|
@ -15,7 +19,7 @@ namespace Jellyfin.Drawing.Skia
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Image encoder that uses <see cref="SkiaSharp"/> to manipulate images.
|
/// Image encoder that uses <see cref="SkiaSharp"/> to manipulate images.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SkiaEncoder : IImageEncoder
|
public class SkiaEncoder : CoreEncoder, IImageEncoder
|
||||||
{
|
{
|
||||||
private static readonly HashSet<string> _transparentImageTypes
|
private static readonly HashSet<string> _transparentImageTypes
|
||||||
= new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".png", ".gif", ".webp" };
|
= new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".png", ".gif", ".webp" };
|
||||||
|
@ -229,6 +233,48 @@ namespace Jellyfin.Drawing.Skia
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
/// <exception cref="ArgumentNullException">The path is null.</exception>
|
||||||
|
/// <exception cref="FileNotFoundException">The path is not valid.</exception>
|
||||||
|
/// <exception cref="SkiaCodecException">The file at the specified path could not be used to generate a codec.</exception>
|
||||||
|
[SuppressMessage("Microsoft.Performance", "CA1814:PreferJaggedArraysOverMultidimensional")]
|
||||||
|
public string GetImageHash(string path)
|
||||||
|
{
|
||||||
|
if (path == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!File.Exists(path))
|
||||||
|
{
|
||||||
|
throw new FileNotFoundException("File not found", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var bitmap = GetBitmap(path, false, false, null))
|
||||||
|
{
|
||||||
|
if (bitmap == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException($"Skia unable to read image {path}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var width = bitmap.Width;
|
||||||
|
var height = bitmap.Height;
|
||||||
|
var pixels = new Pixel[width, height];
|
||||||
|
Parallel.ForEach(Enumerable.Range(0, height), y =>
|
||||||
|
{
|
||||||
|
for (var x = 0; x < width; x++)
|
||||||
|
{
|
||||||
|
var color = bitmap.GetPixel(x, y);
|
||||||
|
pixels[x, y].Red = MathUtils.SRgbToLinear(color.Red);
|
||||||
|
pixels[x, y].Green = MathUtils.SRgbToLinear(color.Green);
|
||||||
|
pixels[x, y].Blue = MathUtils.SRgbToLinear(color.Blue);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return CoreEncode(pixels, 4, 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static bool HasDiacritics(string text)
|
private static bool HasDiacritics(string text)
|
||||||
=> !string.Equals(text, text.RemoveDiacritics(), StringComparison.Ordinal);
|
=> !string.Equals(text, text.RemoveDiacritics(), StringComparison.Ordinal);
|
||||||
|
|
||||||
|
|
|
@ -323,6 +323,7 @@ namespace MediaBrowser.Api.Images
|
||||||
{
|
{
|
||||||
int? width = null;
|
int? width = null;
|
||||||
int? height = null;
|
int? height = null;
|
||||||
|
string? blurhash = null;
|
||||||
long length = 0;
|
long length = 0;
|
||||||
|
|
||||||
try
|
try
|
||||||
|
@ -332,7 +333,10 @@ namespace MediaBrowser.Api.Images
|
||||||
var fileInfo = _fileSystem.GetFileInfo(info.Path);
|
var fileInfo = _fileSystem.GetFileInfo(info.Path);
|
||||||
length = fileInfo.Length;
|
length = fileInfo.Length;
|
||||||
|
|
||||||
ImageDimensions size = _imageProcessor.GetImageDimensions(item, info);
|
blurhash = _imageProcessor.GetImageHash(info.Path);
|
||||||
|
info.Hash = blurhash; // TODO: this doesn't seem like the right thing to do
|
||||||
|
|
||||||
|
ImageDimensions size = _imageProcessor.GetImageDimensions(item, info, true);
|
||||||
_libraryManager.UpdateImages(item);
|
_libraryManager.UpdateImages(item);
|
||||||
width = size.Width;
|
width = size.Width;
|
||||||
height = size.Height;
|
height = size.Height;
|
||||||
|
@ -358,6 +362,7 @@ namespace MediaBrowser.Api.Images
|
||||||
ImageType = info.Type,
|
ImageType = info.Type,
|
||||||
ImageTag = _imageProcessor.GetImageCacheTag(item, info),
|
ImageTag = _imageProcessor.GetImageCacheTag(item, info),
|
||||||
Size = length,
|
Size = length,
|
||||||
|
Hash = blurhash,
|
||||||
Width = width,
|
Width = width,
|
||||||
Height = height
|
Height = height
|
||||||
};
|
};
|
||||||
|
|
|
@ -43,6 +43,13 @@ namespace MediaBrowser.Controller.Drawing
|
||||||
/// <returns>The image dimensions.</returns>
|
/// <returns>The image dimensions.</returns>
|
||||||
ImageDimensions GetImageSize(string path);
|
ImageDimensions GetImageSize(string path);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the blurhash of an image.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">The filepath of the image.</param>
|
||||||
|
/// <returns>The blurhash.</returns>
|
||||||
|
string GetImageHash(string path);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Encode an image.
|
/// Encode an image.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -32,6 +32,13 @@ namespace MediaBrowser.Controller.Drawing
|
||||||
/// <returns>ImageDimensions</returns>
|
/// <returns>ImageDimensions</returns>
|
||||||
ImageDimensions GetImageDimensions(string path);
|
ImageDimensions GetImageDimensions(string path);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the blurhash of the image.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">Path to the image file.</param>
|
||||||
|
/// <returns>BlurHash</returns>
|
||||||
|
String GetImageHash(string path);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the dimensions of the image.
|
/// Gets the dimensions of the image.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -2222,6 +2222,7 @@ namespace MediaBrowser.Controller.Entities
|
||||||
existingImage.DateModified = image.DateModified;
|
existingImage.DateModified = image.DateModified;
|
||||||
existingImage.Width = image.Width;
|
existingImage.Width = image.Width;
|
||||||
existingImage.Height = image.Height;
|
existingImage.Height = image.Height;
|
||||||
|
existingImage.Hash = image.Hash;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -28,6 +28,12 @@ namespace MediaBrowser.Controller.Entities
|
||||||
|
|
||||||
public int Height { get; set; }
|
public int Height { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the blurhash.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The blurhash.</value>
|
||||||
|
public string Hash { get; set; }
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public bool IsLocalFile => Path == null || !Path.StartsWith("http", StringComparison.OrdinalIgnoreCase);
|
public bool IsLocalFile => Path == null || !Path.StartsWith("http", StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
|
@ -510,6 +510,12 @@ namespace MediaBrowser.Model.Dto
|
||||||
/// <value>The series thumb image tag.</value>
|
/// <value>The series thumb image tag.</value>
|
||||||
public string SeriesThumbImageTag { get; set; }
|
public string SeriesThumbImageTag { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the blurhash for the image tags.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The blurhashes.</value>
|
||||||
|
public Dictionary<string, string> ImageHashes { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the series studio.
|
/// Gets or sets the series studio.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -30,6 +30,12 @@ namespace MediaBrowser.Model.Dto
|
||||||
/// <value>The path.</value>
|
/// <value>The path.</value>
|
||||||
public string Path { get; set; }
|
public string Path { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the blurhash.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The blurhash.</value>
|
||||||
|
public string Hash { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the height.
|
/// Gets or sets the height.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user