mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-05-02 06:46:31 +01:00
Merge pull request #2676 from GranPC/public-pr/blurhash
Implement Blurhash generation for images
This commit is contained in:
@@ -1141,24 +1141,24 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
public string ToValueString(ItemImageInfo image)
|
||||
{
|
||||
var delimeter = "*";
|
||||
const string Delimeter = "*";
|
||||
|
||||
var path = image.Path;
|
||||
|
||||
if (path == null)
|
||||
{
|
||||
path = string.Empty;
|
||||
}
|
||||
var path = image.Path ?? string.Empty;
|
||||
var hash = image.BlurHash ?? string.Empty;
|
||||
|
||||
return GetPathToSave(path) +
|
||||
delimeter +
|
||||
Delimeter +
|
||||
image.DateModified.Ticks.ToString(CultureInfo.InvariantCulture) +
|
||||
delimeter +
|
||||
Delimeter +
|
||||
image.Type +
|
||||
delimeter +
|
||||
Delimeter +
|
||||
image.Width.ToString(CultureInfo.InvariantCulture) +
|
||||
delimeter +
|
||||
image.Height.ToString(CultureInfo.InvariantCulture);
|
||||
Delimeter +
|
||||
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)
|
||||
@@ -1192,6 +1192,11 @@ namespace Emby.Server.Implementations.Data
|
||||
image.Width = width;
|
||||
image.Height = height;
|
||||
}
|
||||
|
||||
if (parts.Length >= 6)
|
||||
{
|
||||
image.BlurHash = parts[5].Replace('/', '*').Replace('\\', '|');
|
||||
}
|
||||
}
|
||||
|
||||
return image;
|
||||
|
||||
@@ -605,7 +605,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
|
||||
if (dictionary.TryGetValue(person.Name, out Person entity))
|
||||
{
|
||||
baseItemPerson.PrimaryImageTag = GetImageCacheTag(entity, ImageType.Primary);
|
||||
baseItemPerson.PrimaryImageTag = GetTagAndFillBlurhash(dto, entity, ImageType.Primary);
|
||||
baseItemPerson.Id = entity.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||
list.Add(baseItemPerson);
|
||||
}
|
||||
@@ -654,6 +654,70 @@ namespace Emby.Server.Implementations.Dto
|
||||
return _libraryManager.GetGenreId(name);
|
||||
}
|
||||
|
||||
private string GetTagAndFillBlurhash(BaseItemDto dto, BaseItem item, ImageType imageType, int imageIndex = 0)
|
||||
{
|
||||
var image = item.GetImageInfo(imageType, imageIndex);
|
||||
if (image != null)
|
||||
{
|
||||
return GetTagAndFillBlurhash(dto, item, image);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string GetTagAndFillBlurhash(BaseItemDto dto, BaseItem item, ItemImageInfo image)
|
||||
{
|
||||
var tag = GetImageCacheTag(item, image);
|
||||
if (!string.IsNullOrEmpty(image.BlurHash))
|
||||
{
|
||||
if (dto.ImageBlurHashes == null)
|
||||
{
|
||||
dto.ImageBlurHashes = new Dictionary<ImageType, Dictionary<string, string>>();
|
||||
}
|
||||
|
||||
if (!dto.ImageBlurHashes.ContainsKey(image.Type))
|
||||
{
|
||||
dto.ImageBlurHashes[image.Type] = new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
dto.ImageBlurHashes[image.Type][tag] = image.BlurHash;
|
||||
}
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
private string[] GetTagsAndFillBlurhashes(BaseItemDto dto, BaseItem item, ImageType imageType, int limit)
|
||||
{
|
||||
return GetTagsAndFillBlurhashes(dto, item, imageType, item.GetImages(imageType).Take(limit).ToList());
|
||||
}
|
||||
|
||||
private string[] GetTagsAndFillBlurhashes(BaseItemDto dto, BaseItem item, ImageType imageType, List<ItemImageInfo> images)
|
||||
{
|
||||
var tags = GetImageTags(item, images);
|
||||
var hashes = new Dictionary<string, string>();
|
||||
for (int i = 0; i < images.Count; i++)
|
||||
{
|
||||
var img = images[i];
|
||||
if (!string.IsNullOrEmpty(img.BlurHash))
|
||||
{
|
||||
var tag = tags[i];
|
||||
hashes[tag] = img.BlurHash;
|
||||
}
|
||||
}
|
||||
|
||||
if (hashes.Count > 0)
|
||||
{
|
||||
if (dto.ImageBlurHashes == null)
|
||||
{
|
||||
dto.ImageBlurHashes = new Dictionary<ImageType, Dictionary<string, string>>();
|
||||
}
|
||||
|
||||
dto.ImageBlurHashes[imageType] = hashes;
|
||||
}
|
||||
|
||||
return tags;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets simple property values on a DTOBaseItem
|
||||
/// </summary>
|
||||
@@ -674,8 +738,8 @@ namespace Emby.Server.Implementations.Dto
|
||||
dto.LockData = item.IsLocked;
|
||||
dto.ForcedSortName = item.ForcedSortName;
|
||||
}
|
||||
dto.Container = item.Container;
|
||||
|
||||
dto.Container = item.Container;
|
||||
dto.EndDate = item.EndDate;
|
||||
|
||||
if (options.ContainsField(ItemFields.ExternalUrls))
|
||||
@@ -694,10 +758,12 @@ namespace Emby.Server.Implementations.Dto
|
||||
dto.AspectRatio = hasAspectRatio.AspectRatio;
|
||||
}
|
||||
|
||||
dto.ImageBlurHashes = new Dictionary<ImageType, Dictionary<string, string>>();
|
||||
|
||||
var backdropLimit = options.GetImageLimit(ImageType.Backdrop);
|
||||
if (backdropLimit > 0)
|
||||
{
|
||||
dto.BackdropImageTags = GetImageTags(item, item.GetImages(ImageType.Backdrop).Take(backdropLimit).ToList());
|
||||
dto.BackdropImageTags = GetTagsAndFillBlurhashes(dto, item, ImageType.Backdrop, backdropLimit);
|
||||
}
|
||||
|
||||
if (options.ContainsField(ItemFields.ScreenshotImageTags))
|
||||
@@ -705,7 +771,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
var screenshotLimit = options.GetImageLimit(ImageType.Screenshot);
|
||||
if (screenshotLimit > 0)
|
||||
{
|
||||
dto.ScreenshotImageTags = GetImageTags(item, item.GetImages(ImageType.Screenshot).Take(screenshotLimit).ToList());
|
||||
dto.ScreenshotImageTags = GetTagsAndFillBlurhashes(dto, item, ImageType.Screenshot, screenshotLimit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -721,12 +787,11 @@ namespace Emby.Server.Implementations.Dto
|
||||
|
||||
// Prevent implicitly captured closure
|
||||
var currentItem = item;
|
||||
foreach (var image in currentItem.ImageInfos.Where(i => !currentItem.AllowsMultipleImages(i.Type))
|
||||
.ToList())
|
||||
foreach (var image in currentItem.ImageInfos.Where(i => !currentItem.AllowsMultipleImages(i.Type)))
|
||||
{
|
||||
if (options.GetImageLimit(image.Type) > 0)
|
||||
{
|
||||
var tag = GetImageCacheTag(item, image);
|
||||
var tag = GetTagAndFillBlurhash(dto, item, image);
|
||||
|
||||
if (tag != null)
|
||||
{
|
||||
@@ -871,8 +936,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
if (albumParent != null)
|
||||
{
|
||||
dto.AlbumId = albumParent.Id;
|
||||
|
||||
dto.AlbumPrimaryImageTag = GetImageCacheTag(albumParent, ImageType.Primary);
|
||||
dto.AlbumPrimaryImageTag = GetTagAndFillBlurhash(dto, albumParent, ImageType.Primary);
|
||||
}
|
||||
|
||||
//if (options.ContainsField(ItemFields.MediaSourceCount))
|
||||
@@ -1099,7 +1163,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
episodeSeries = episodeSeries ?? episode.Series;
|
||||
if (episodeSeries != null)
|
||||
{
|
||||
dto.SeriesPrimaryImageTag = GetImageCacheTag(episodeSeries, ImageType.Primary);
|
||||
dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, episodeSeries, ImageType.Primary);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1145,7 +1209,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
series = series ?? season.Series;
|
||||
if (series != null)
|
||||
{
|
||||
dto.SeriesPrimaryImageTag = GetImageCacheTag(series, ImageType.Primary);
|
||||
dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, series, ImageType.Primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1275,7 +1339,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
if (image != null)
|
||||
{
|
||||
dto.ParentLogoItemId = GetDtoId(parent);
|
||||
dto.ParentLogoImageTag = GetImageCacheTag(parent, image);
|
||||
dto.ParentLogoImageTag = GetTagAndFillBlurhash(dto, parent, image);
|
||||
}
|
||||
}
|
||||
if (artLimit > 0 && !(imageTags != null && imageTags.ContainsKey(ImageType.Art)) && dto.ParentArtItemId == null)
|
||||
@@ -1285,7 +1349,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
if (image != null)
|
||||
{
|
||||
dto.ParentArtItemId = GetDtoId(parent);
|
||||
dto.ParentArtImageTag = GetImageCacheTag(parent, image);
|
||||
dto.ParentArtImageTag = GetTagAndFillBlurhash(dto, parent, image);
|
||||
}
|
||||
}
|
||||
if (thumbLimit > 0 && !(imageTags != null && imageTags.ContainsKey(ImageType.Thumb)) && (dto.ParentThumbItemId == null || parent is Series) && !(parent is ICollectionFolder) && !(parent is UserView))
|
||||
@@ -1295,7 +1359,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
if (image != null)
|
||||
{
|
||||
dto.ParentThumbItemId = GetDtoId(parent);
|
||||
dto.ParentThumbImageTag = GetImageCacheTag(parent, image);
|
||||
dto.ParentThumbImageTag = GetTagAndFillBlurhash(dto, parent, image);
|
||||
}
|
||||
}
|
||||
if (backdropLimit > 0 && !((dto.BackdropImageTags != null && dto.BackdropImageTags.Length > 0) || (dto.ParentBackdropImageTags != null && dto.ParentBackdropImageTags.Length > 0)))
|
||||
@@ -1305,7 +1369,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
if (images.Count > 0)
|
||||
{
|
||||
dto.ParentBackdropItemId = GetDtoId(parent);
|
||||
dto.ParentBackdropImageTags = GetImageTags(parent, images);
|
||||
dto.ParentBackdropImageTags = GetTagsAndFillBlurhashes(dto, parent, ImageType.Backdrop, images);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Progress;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
@@ -35,6 +36,7 @@ using MediaBrowser.Controller.Resolvers;
|
||||
using MediaBrowser.Controller.Sorting;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Drawing;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
@@ -67,6 +69,7 @@ namespace Emby.Server.Implementations.Library
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IItemRepository _itemRepository;
|
||||
private readonly ConcurrentDictionary<Guid, BaseItem> _libraryItemsCache;
|
||||
private readonly IImageProcessor _imageProcessor;
|
||||
|
||||
private NamingOptions _namingOptions;
|
||||
private string[] _videoFileExtensions;
|
||||
@@ -147,7 +150,8 @@ namespace Emby.Server.Implementations.Library
|
||||
Lazy<IProviderManager> providerManagerFactory,
|
||||
Lazy<IUserViewManager> userviewManagerFactory,
|
||||
IMediaEncoder mediaEncoder,
|
||||
IItemRepository itemRepository)
|
||||
IItemRepository itemRepository,
|
||||
IImageProcessor imageProcessor)
|
||||
{
|
||||
_appHost = appHost;
|
||||
_logger = logger;
|
||||
@@ -161,6 +165,7 @@ namespace Emby.Server.Implementations.Library
|
||||
_userviewManagerFactory = userviewManagerFactory;
|
||||
_mediaEncoder = mediaEncoder;
|
||||
_itemRepository = itemRepository;
|
||||
_imageProcessor = imageProcessor;
|
||||
|
||||
_libraryItemsCache = new ConcurrentDictionary<Guid, BaseItem>();
|
||||
|
||||
@@ -1815,10 +1820,90 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateImages(BaseItem item)
|
||||
private bool ImageNeedsRefresh(ItemImageInfo image)
|
||||
{
|
||||
_itemRepository.SaveImages(item);
|
||||
if (image.Path != null && image.IsLocalFile)
|
||||
{
|
||||
if (image.Width == 0 || image.Height == 0 || string.IsNullOrEmpty(image.BlurHash))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return _fileSystem.GetLastWriteTimeUtc(image.Path) != image.DateModified;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Cannot get file info for {0}", image.Path);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return image.Path != null && !image.IsLocalFile;
|
||||
}
|
||||
|
||||
public void UpdateImages(BaseItem item, bool forceUpdate = false)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
}
|
||||
|
||||
var outdated = forceUpdate ? item.ImageInfos.Where(i => i.Path != null).ToArray() : item.ImageInfos.Where(ImageNeedsRefresh).ToArray();
|
||||
if (outdated.Length == 0)
|
||||
{
|
||||
RegisterItem(item);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var img in outdated)
|
||||
{
|
||||
var image = img;
|
||||
if (!img.IsLocalFile)
|
||||
{
|
||||
try
|
||||
{
|
||||
var index = item.GetImageIndex(img);
|
||||
image = ConvertImageToLocal(item, img, index).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
_logger.LogWarning("Cannot get image index for {0}", img.Path);
|
||||
continue;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
_logger.LogWarning("Cannot fetch image from {0}", img.Path);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
ImageDimensions size = _imageProcessor.GetImageDimensions(item, image);
|
||||
image.Width = size.Width;
|
||||
image.Height = size.Height;
|
||||
|
||||
try
|
||||
{
|
||||
image.BlurHash = _imageProcessor.GetImageBlurHash(image.Path);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Cannot compute blurhash for {0}", image.Path);
|
||||
image.BlurHash = string.Empty;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
image.DateModified = _fileSystem.GetLastWriteTimeUtc(image.Path);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Cannot update DateModified for {0}", image.Path);
|
||||
}
|
||||
}
|
||||
|
||||
_itemRepository.SaveImages(item);
|
||||
RegisterItem(item);
|
||||
}
|
||||
|
||||
@@ -1839,7 +1924,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
item.DateLastSaved = DateTime.UtcNow;
|
||||
|
||||
RegisterItem(item);
|
||||
UpdateImages(item, updateReason >= ItemUpdateType.ImageUpdate);
|
||||
}
|
||||
|
||||
_itemRepository.SaveItems(itemsList, cancellationToken);
|
||||
|
||||
Reference in New Issue
Block a user