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); /// /// Creates a Thumbnail version of a base64 image /// /// base64 encoded image /// /// File name with extension of the file. This will always write to string CreateThumbnailFromBase64(string encodedImage, string fileName); string WriteCoverThumbnail(Stream stream, string fileName, string outputDirectory, bool saveAsWebP = false); /// /// Converts the passed image to webP and outputs it in the same directory /// /// Full path to the image to convert /// Where to output the file /// File of written webp image Task ConvertToWebP(string filePath, string outputPath); Task IsImage(string filePath); } public class ImageService : IImageService { private readonly ILogger _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+"; /// /// Width of the Thumbnail generation /// private const int ThumbnailWidth = 320; public ImageService(ILogger 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; } /// /// Creates a thumbnail out of a memory stream and saves to with the passed /// fileName and .png extension. /// /// Stream to write to disk. Ensure this is rewinded. /// filename to save as without extension /// Where to output the file, defaults to covers directory /// Export the file as webP otherwise will default to png /// File name with extension of the file. This will always write to 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 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 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; } /// 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; } /// /// Returns the name format for a chapter cover image /// /// /// /// public static string GetChapterFormat(int chapterId, int volumeId) { return $"v{volumeId}_c{chapterId}"; } /// /// Returns the name format for a library cover image /// /// /// public static string GetLibraryFormat(int libraryId) { return $"l{libraryId}"; } /// /// Returns the name format for a series cover image /// /// /// public static string GetSeriesFormat(int seriesId) { return $"series{seriesId}"; } /// /// Returns the name format for a collection tag cover image /// /// /// public static string GetCollectionTagFormat(int tagId) { return $"tag{tagId}"; } /// /// Returns the name format for a reading list cover image /// /// /// public static string GetReadingListFormat(int readingListId) { return $"readinglist{readingListId}"; } }