Metadata Downloading (#3525)

This commit is contained in:
Joe Milazzo 2025-02-05 16:16:44 -06:00 committed by GitHub
parent eb66763078
commit f4fd7230ea
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
108 changed files with 6296 additions and 484 deletions

View file

@ -4,10 +4,12 @@ using System.IO;
using System.Linq;
using System.Threading.Tasks;
using API.Constants;
using API.Data;
using API.Data.Repositories;
using API.Entities;
using API.Entities.Enums;
using API.Extensions;
using API.SignalR;
using EasyCaching.Core;
using Flurl;
using Flurl.Http;
@ -25,6 +27,8 @@ public interface ICoverDbService
Task<string> DownloadFaviconAsync(string url, EncodeFormat encodeFormat);
Task<string> DownloadPublisherImageAsync(string publisherName, EncodeFormat encodeFormat);
Task<string?> DownloadPersonImageAsync(Person person, EncodeFormat encodeFormat);
Task<string?> DownloadPersonImageAsync(Person person, EncodeFormat encodeFormat, string url);
Task SetPersonCoverImage(Person person, string url, bool fromBase64 = true);
}
@ -34,6 +38,9 @@ public class CoverDbService : ICoverDbService
private readonly IDirectoryService _directoryService;
private readonly IEasyCachingProviderFactory _cacheFactory;
private readonly IHostEnvironment _env;
private readonly IImageService _imageService;
private readonly IUnitOfWork _unitOfWork;
private readonly IEventHub _eventHub;
private const string NewHost = "https://www.kavitareader.com/CoversDB/";
@ -57,12 +64,16 @@ public class CoverDbService : ICoverDbService
private static readonly TimeSpan CacheDuration = TimeSpan.FromDays(1);
public CoverDbService(ILogger<CoverDbService> logger, IDirectoryService directoryService,
IEasyCachingProviderFactory cacheFactory, IHostEnvironment env)
IEasyCachingProviderFactory cacheFactory, IHostEnvironment env, IImageService imageService,
IUnitOfWork unitOfWork, IEventHub eventHub)
{
_logger = logger;
_directoryService = directoryService;
_cacheFactory = cacheFactory;
_env = env;
_imageService = imageService;
_unitOfWork = unitOfWork;
_eventHub = eventHub;
}
public async Task<string> DownloadFaviconAsync(string url, EncodeFormat encodeFormat)
@ -225,36 +236,37 @@ public class CoverDbService : ICoverDbService
{
throw new KavitaException($"Could not grab person image for {person.Name}");
}
return await DownloadPersonImageAsync(person, encodeFormat, personImageLink);
} catch (Exception ex)
{
_logger.LogError(ex, "Error downloading image for {PersonName}", person.Name);
}
// Create the destination file path
var filename = ImageService.GetPersonFormat(person.Id) + encodeFormat.GetExtension();
var targetFile = Path.Combine(_directoryService.CoverImageDirectory, filename);
return null;
}
// Ensure if file exists, we delete to overwrite
_logger.LogTrace("Fetching publisher image from {Url}", personImageLink.Sanitize());
// Download the file using Flurl
var personStream = await personImageLink
.AllowHttpStatus("2xx,304")
.GetStreamAsync();
using var image = Image.NewFromStream(personStream);
switch (encodeFormat)
/// <summary>
/// Attempts to download the Person cover image from a Url
/// </summary>
/// <param name="person"></param>
/// <param name="encodeFormat"></param>
/// <param name="url"></param>
/// <returns></returns>
/// <exception cref="KavitaException"></exception>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public async Task<string?> DownloadPersonImageAsync(Person person, EncodeFormat encodeFormat, string url)
{
try
{
var personImageLink = await GetCoverPersonImagePath(person);
if (string.IsNullOrEmpty(personImageLink))
{
case EncodeFormat.PNG:
image.Pngsave(targetFile);
break;
case EncodeFormat.WEBP:
image.Webpsave(targetFile);
break;
case EncodeFormat.AVIF:
image.Heifsave(targetFile);
break;
default:
throw new ArgumentOutOfRangeException(nameof(encodeFormat), encodeFormat, null);
throw new KavitaException($"Could not grab person image for {person.Name}");
}
var filename = await DownloadImageFromUrl(ImageService.GetPersonFormat(person.Id), encodeFormat, personImageLink);
_logger.LogDebug("Person image for {PersonName} downloaded and saved successfully", person.Name);
return filename;
@ -266,6 +278,39 @@ public class CoverDbService : ICoverDbService
return null;
}
private async Task<string> DownloadImageFromUrl(string filenameWithoutExtension, EncodeFormat encodeFormat, string url)
{
// Create the destination file path
var filename = filenameWithoutExtension + encodeFormat.GetExtension();
var targetFile = Path.Combine(_directoryService.CoverImageDirectory, filename);
// Ensure if file exists, we delete to overwrite
_logger.LogTrace("Fetching person image from {Url}", url.Sanitize());
// Download the file using Flurl
var personStream = await url
.AllowHttpStatus("2xx,304")
.GetStreamAsync();
using var image = Image.NewFromStream(personStream);
switch (encodeFormat)
{
case EncodeFormat.PNG:
image.Pngsave(targetFile);
break;
case EncodeFormat.WEBP:
image.Webpsave(targetFile);
break;
case EncodeFormat.AVIF:
image.Heifsave(targetFile);
break;
default:
throw new ArgumentOutOfRangeException(nameof(encodeFormat), encodeFormat, null);
}
return filename;
}
private async Task<string> GetCoverPersonImagePath(Person person)
{
var tempFile = Path.Join(_directoryService.LongTermCacheDirectory, "people.yml");
@ -414,4 +459,49 @@ public class CoverDbService : ICoverDbService
return null;
}
public async Task SetPersonCoverImage(Person person, string url, bool fromBase64 = true)
{
if (!string.IsNullOrEmpty(url))
{
var filePath = await CreateThumbnail(url, $"{ImageService.GetPersonFormat(person.Id)}", fromBase64);
if (!string.IsNullOrEmpty(filePath))
{
person.CoverImage = filePath;
person.CoverImageLocked = true;
_imageService.UpdateColorScape(person);
_unitOfWork.PersonRepository.Update(person);
}
}
else
{
person.CoverImage = string.Empty;
person.CoverImageLocked = false;
_imageService.UpdateColorScape(person);
_unitOfWork.PersonRepository.Update(person);
}
if (_unitOfWork.HasChanges())
{
await _unitOfWork.CommitAsync();
await _eventHub.SendMessageAsync(MessageFactory.CoverUpdate,
MessageFactory.CoverUpdateEvent(person.Id, MessageFactoryEntityTypes.Person), false);
}
}
private async Task<string> CreateThumbnail(string url, string filename, bool fromBase64 = true)
{
var settings = await _unitOfWork.SettingsRepository.GetSettingsDtoAsync();
var encodeFormat = settings.EncodeMediaAs;
var coverImageSize = settings.CoverImageSize;
if (fromBase64)
{
return _imageService.CreateThumbnailFromBase64(url,
filename, encodeFormat, coverImageSize.GetDimensions().Width);
}
return await DownloadImageFromUrl(filename, encodeFormat, url);
}
}