
* Updating wording on card item when total pages is 0, to be just "Cannot Read" since it could be a non-archive file * Refactored cover images to be stored on disk. This first commit has the extraction to disk and the metadata service to handle updating when applicable. * Refactored code to have the actual save to cover image directory done by ImageService. * Implemented the ability to override cover images. * Some cleanup on Image service * Implemented the ability to cleanup old covers nightly * Added a migration to migrate existing covers to new cover image format (files). * Refactored all CoverImages to just be the filename, leaving the Join with Cover directory to higher level code. * Ensure when getting user progress, we pick the first. * Added cleanup cover images for deleted tags. Don't pull any cover images that are blank. * After series update, clear out cover image. No change on UI, but just keeps things clear before metadata refresh hits * Refactored image formats for covers to ImageService. * Fixed an issue where after refactoring how images were stored, the cleanup service was deleting them after each scan. * Changed how ShouldUpdateCoverImage works to check if file exists or not even if cover image is locked. * Fixed unit tests * Added caching back to cover images. * Caching on series as well * Code Cleanup items * Ensure when checking if a file exists in MetadataService, that we join for cover image directory. After we scan library, do one last filter to delete any series that have 0 pages total. * Catch exceptions so we don't run cover migration if this is first time run. * After a scan, only clear out the cache directory and not do a deep clean. * Implemented the ability to backup custom locked covers only. * Fixed unit tests * Trying to figure out why GA crashes when running MetadataServiceTests.cs * Some debugging on GA tests not running * Commented out tests that were causing issues in GA. * Fixed an issue where series cover images wouldn't migrate * Fixed the updating of links to actually do all series and not just locked
152 lines
4.7 KiB
C#
152 lines
4.7 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using API.Comparators;
|
|
using API.Entities;
|
|
using API.Interfaces.Services;
|
|
using Microsoft.Extensions.Logging;
|
|
using NetVips;
|
|
|
|
namespace API.Services
|
|
{
|
|
|
|
public class ImageService : IImageService
|
|
{
|
|
private readonly ILogger<ImageService> _logger;
|
|
private readonly IDirectoryService _directoryService;
|
|
public const string ChapterCoverImageRegex = @"v\d+_c\d+";
|
|
public const string SeriesCoverImageRegex = @"seres\d+";
|
|
public const string CollectionTagCoverImageRegex = @"tag\d+";
|
|
|
|
|
|
/// <summary>
|
|
/// Width of the Thumbnail generation
|
|
/// </summary>
|
|
private const int ThumbnailWidth = 320;
|
|
|
|
public ImageService(ILogger<ImageService> logger, IDirectoryService directoryService)
|
|
{
|
|
_logger = logger;
|
|
_directoryService = directoryService;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finds the first image in the directory of the first file. Does not check for "cover/folder".ext files to override.
|
|
/// </summary>
|
|
/// <param name="file"></param>
|
|
/// <returns></returns>
|
|
public string GetCoverFile(MangaFile file)
|
|
{
|
|
var directory = Path.GetDirectoryName(file.FilePath);
|
|
if (string.IsNullOrEmpty(directory))
|
|
{
|
|
_logger.LogError("Could not find Directory for {File}", file.FilePath);
|
|
return null;
|
|
}
|
|
|
|
var firstImage = _directoryService.GetFilesWithExtension(directory, Parser.Parser.ImageFileExtensions)
|
|
.OrderBy(f => f, new NaturalSortComparer()).FirstOrDefault();
|
|
|
|
return firstImage;
|
|
}
|
|
|
|
public string GetCoverImage(string path, string fileName)
|
|
{
|
|
if (string.IsNullOrEmpty(path)) return string.Empty;
|
|
|
|
try
|
|
{
|
|
return CreateThumbnail(path, fileName);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "[GetCoverImage] There was an error and prevented thumbnail generation on {ImageFile}. Defaulting to no cover image", path);
|
|
}
|
|
|
|
return string.Empty;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public string CreateThumbnail(string path, string fileName)
|
|
{
|
|
try
|
|
{
|
|
using var thumbnail = Image.Thumbnail(path, ThumbnailWidth);
|
|
var filename = fileName + ".png";
|
|
thumbnail.WriteToFile(Path.Join(DirectoryService.CoverImageDirectory, fileName + ".png"));
|
|
return filename;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
_logger.LogError(e, "Error creating thumbnail from url");
|
|
}
|
|
|
|
return string.Empty;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a thumbnail out of a memory stream and saves to <see cref="DirectoryService.CoverImageDirectory"/> with the passed
|
|
/// fileName and .png extension.
|
|
/// </summary>
|
|
/// <param name="stream">Stream to write to disk. Ensure this is rewinded.</param>
|
|
/// <param name="fileName">filename to save as without extension</param>
|
|
/// <returns>File name with extension of the file. This will always write to <see cref="DirectoryService.CoverImageDirectory"/></returns>
|
|
public static string WriteCoverThumbnail(Stream stream, string fileName)
|
|
{
|
|
using var thumbnail = NetVips.Image.ThumbnailStream(stream, ThumbnailWidth);
|
|
var filename = fileName + ".png";
|
|
thumbnail.WriteToFile(Path.Join(DirectoryService.CoverImageDirectory, fileName + ".png"));
|
|
return filename;
|
|
}
|
|
|
|
|
|
/// <inheritdoc />
|
|
public string CreateThumbnailFromBase64(string encodedImage, string fileName)
|
|
{
|
|
try
|
|
{
|
|
using var thumbnail = Image.ThumbnailBuffer(Convert.FromBase64String(encodedImage), ThumbnailWidth);
|
|
var filename = fileName + ".png";
|
|
thumbnail.WriteToFile(Path.Join(DirectoryService.CoverImageDirectory, fileName + ".png"));
|
|
return filename;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
_logger.LogError(e, "Error creating thumbnail from url");
|
|
}
|
|
|
|
return string.Empty;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the name format for a chapter cover image
|
|
/// </summary>
|
|
/// <param name="chapterId"></param>
|
|
/// <param name="volumeId"></param>
|
|
/// <returns></returns>
|
|
public static string GetChapterFormat(int chapterId, int volumeId)
|
|
{
|
|
return $"v{volumeId}_c{chapterId}";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the name format for a series cover image
|
|
/// </summary>
|
|
/// <param name="seriesId"></param>
|
|
/// <returns></returns>
|
|
public static string GetSeriesFormat(int seriesId)
|
|
{
|
|
return $"series{seriesId}";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the name format for a collection tag cover image
|
|
/// </summary>
|
|
/// <param name="tagId"></param>
|
|
/// <returns></returns>
|
|
public static string GetCollectionTagFormat(int tagId)
|
|
{
|
|
return $"tag{tagId}";
|
|
}
|
|
}
|
|
}
|