
* Implemented save covers as webp. Reworked screen to provide more information up front about webp and what browsers can support it. * cleaned up pages to use compact numbering and made compact numbering expand into one decimal place (20.5K) * Fixed an issue with adding new device * If a book has an invalid language set, drop the language altogether rather than reading in a corrupted entry. * Ensure genres and tags render alphabetically. Improved support for partial volumes in Comic parser. * Ensure all people, tags, collections, and genres are in alphabetical order. * Moved some code to Extensions to clean up code. * More unit tests * Cleaned up release year filter css * Tweaked some code in all series to make bulk deletes cleaner on the UI. * Trying out want to read and unread count on series detail page * Added Want to Read button for series page to make it easy to see when something is in want to read list and toggle it. Added tooltips instead of title to buttons, but they don't style correctly. Added a continue point under cover image. * Code smells
201 lines
7.1 KiB
C#
201 lines
7.1 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.Extensions.Logging;
|
|
using SixLabors.ImageSharp;
|
|
using Image = NetVips.Image;
|
|
|
|
namespace API.Services;
|
|
|
|
public interface IImageService
|
|
{
|
|
void ExtractImages(string fileFilePath, string targetDirectory, int fileCount = 1);
|
|
string GetCoverImage(string path, string fileName, string outputDirectory, bool saveAsWebP = false);
|
|
|
|
/// <summary>
|
|
/// Creates a Thumbnail version of a base64 image
|
|
/// </summary>
|
|
/// <param name="encodedImage">base64 encoded image</param>
|
|
/// <param name="fileName"></param>
|
|
/// <returns>File name with extension of the file. This will always write to <see cref="DirectoryService.CoverImageDirectory"/></returns>
|
|
string CreateThumbnailFromBase64(string encodedImage, string fileName);
|
|
|
|
string WriteCoverThumbnail(Stream stream, string fileName, string outputDirectory, bool saveAsWebP = false);
|
|
/// <summary>
|
|
/// Converts the passed image to webP and outputs it in the same directory
|
|
/// </summary>
|
|
/// <param name="filePath">Full path to the image to convert</param>
|
|
/// <param name="outputPath">Where to output the file</param>
|
|
/// <returns>File of written webp image</returns>
|
|
Task<string> ConvertToWebP(string filePath, string outputPath);
|
|
|
|
Task<bool> IsImage(string filePath);
|
|
}
|
|
|
|
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 = @"series\d+";
|
|
public const string CollectionTagCoverImageRegex = @"tag\d+";
|
|
public const string ReadingListCoverImageRegex = @"readinglist\d+";
|
|
|
|
|
|
/// <summary>
|
|
/// Width of the Thumbnail generation
|
|
/// </summary>
|
|
private const int ThumbnailWidth = 320;
|
|
|
|
public ImageService(ILogger<ImageService> logger, IDirectoryService directoryService)
|
|
{
|
|
_logger = logger;
|
|
_directoryService = directoryService;
|
|
}
|
|
|
|
public void ExtractImages(string fileFilePath, string targetDirectory, int fileCount = 1)
|
|
{
|
|
_directoryService.ExistOrCreate(targetDirectory);
|
|
if (fileCount == 1)
|
|
{
|
|
_directoryService.CopyFileToDirectory(fileFilePath, targetDirectory);
|
|
}
|
|
else
|
|
{
|
|
_directoryService.CopyDirectoryToDirectory(Path.GetDirectoryName(fileFilePath), targetDirectory,
|
|
Tasks.Scanner.Parser.Parser.ImageFileExtensions);
|
|
}
|
|
}
|
|
|
|
public string GetCoverImage(string path, string fileName, string outputDirectory, bool saveAsWebP = false)
|
|
{
|
|
if (string.IsNullOrEmpty(path)) return string.Empty;
|
|
|
|
try
|
|
{
|
|
using var thumbnail = Image.Thumbnail(path, ThumbnailWidth);
|
|
var filename = fileName + (saveAsWebP ? ".webp" : ".png");
|
|
thumbnail.WriteToFile(_directoryService.FileSystem.Path.Join(outputDirectory, filename));
|
|
return 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;
|
|
}
|
|
|
|
/// <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>
|
|
/// <param name="outputDirectory">Where to output the file, defaults to covers directory</param>
|
|
/// <param name="saveAsWebP">Export the file as webP otherwise will default to png</param>
|
|
/// <returns>File name with extension of the file. This will always write to <see cref="DirectoryService.CoverImageDirectory"/></returns>
|
|
public string WriteCoverThumbnail(Stream stream, string fileName, string outputDirectory, bool saveAsWebP = false)
|
|
{
|
|
using var thumbnail = Image.ThumbnailStream(stream, ThumbnailWidth);
|
|
var filename = fileName + (saveAsWebP ? ".webp" : ".png");
|
|
_directoryService.ExistOrCreate(outputDirectory);
|
|
try
|
|
{
|
|
_directoryService.FileSystem.File.Delete(_directoryService.FileSystem.Path.Join(outputDirectory, filename));
|
|
} catch (Exception) {/* Swallow exception */}
|
|
thumbnail.WriteToFile(_directoryService.FileSystem.Path.Join(outputDirectory, filename));
|
|
return filename;
|
|
}
|
|
|
|
public async Task<string> ConvertToWebP(string filePath, string outputPath)
|
|
{
|
|
var file = _directoryService.FileSystem.FileInfo.FromFileName(filePath);
|
|
var fileName = file.Name.Replace(file.Extension, string.Empty);
|
|
var outputFile = Path.Join(outputPath, fileName + ".webp");
|
|
|
|
|
|
using var sourceImage = await SixLabors.ImageSharp.Image.LoadAsync(filePath);
|
|
await sourceImage.SaveAsWebpAsync(outputFile);
|
|
return outputFile;
|
|
}
|
|
|
|
public async Task<bool> IsImage(string filePath)
|
|
{
|
|
try
|
|
{
|
|
var info = await SixLabors.ImageSharp.Image.IdentifyAsync(filePath);
|
|
if (info == null) return false;
|
|
|
|
return true;
|
|
}
|
|
catch (Exception)
|
|
{
|
|
/* Swallow Exception */
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/// <inheritdoc />
|
|
public string CreateThumbnailFromBase64(string encodedImage, string fileName)
|
|
{
|
|
try
|
|
{
|
|
using var thumbnail = Image.ThumbnailBuffer(Convert.FromBase64String(encodedImage), ThumbnailWidth);
|
|
var filename = fileName + ".png";
|
|
thumbnail.WriteToFile(_directoryService.FileSystem.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}";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the name format for a reading list cover image
|
|
/// </summary>
|
|
/// <param name="readingListId"></param>
|
|
/// <returns></returns>
|
|
public static string GetReadingListFormat(int readingListId)
|
|
{
|
|
return $"readinglist{readingListId}";
|
|
}
|
|
|
|
|
|
}
|