mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
Implemented the ability to allow the admin to change the cover generation size. (#2213)
Changed how covers are merged together. Now a cover image will always be generated for reading list and collections. Fixed reading list page being a bit laggy due to a missing trackby function. Reading list page will now show the cover image always. Collection detail page will only hide the image if there is no summary on the collection.
This commit is contained in:
parent
19801af6f3
commit
d134196470
@ -42,7 +42,7 @@ internal class MockReadingItemServiceForCacheService : IReadingItemService
|
||||
return 1;
|
||||
}
|
||||
|
||||
public string GetCoverImage(string fileFilePath, string fileName, MangaFormat format, EncodeFormat encodeFormat)
|
||||
public string GetCoverImage(string fileFilePath, string fileName, MangaFormat format, EncodeFormat encodeFormat, CoverImageSize size = CoverImageSize.Default)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ internal class MockReadingItemService : IReadingItemService
|
||||
return 1;
|
||||
}
|
||||
|
||||
public string GetCoverImage(string fileFilePath, string fileName, MangaFormat format, EncodeFormat encodeFormat)
|
||||
public string GetCoverImage(string fileFilePath, string fileName, MangaFormat format, EncodeFormat encodeFormat, CoverImageSize size = CoverImageSize.Default)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
@ -158,11 +158,6 @@ public class ImageController : BaseApiController
|
||||
private async Task<string> GenerateReadingListCoverImage(int readingListId)
|
||||
{
|
||||
var covers = await _unitOfWork.ReadingListRepository.GetRandomCoverImagesAsync(readingListId);
|
||||
if (covers.Count < 4)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var destFile = _directoryService.FileSystem.Path.Join(_directoryService.TempDirectory,
|
||||
ImageService.GetReadingListFormat(readingListId));
|
||||
var settings = await _unitOfWork.SettingsRepository.GetSettingsDtoAsync();
|
||||
@ -171,6 +166,7 @@ public class ImageController : BaseApiController
|
||||
if (_directoryService.FileSystem.File.Exists(destFile)) return destFile;
|
||||
ImageService.CreateMergedImage(
|
||||
covers.Select(c => _directoryService.FileSystem.Path.Join(_directoryService.CoverImageDirectory, c)).ToList(),
|
||||
settings.CoverImageSize,
|
||||
destFile);
|
||||
return !_directoryService.FileSystem.File.Exists(destFile) ? string.Empty : destFile;
|
||||
}
|
||||
@ -178,11 +174,6 @@ public class ImageController : BaseApiController
|
||||
private async Task<string> GenerateCollectionCoverImage(int collectionId)
|
||||
{
|
||||
var covers = await _unitOfWork.CollectionTagRepository.GetRandomCoverImagesAsync(collectionId);
|
||||
if (covers.Count < 4)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var destFile = _directoryService.FileSystem.Path.Join(_directoryService.TempDirectory,
|
||||
ImageService.GetCollectionTagFormat(collectionId));
|
||||
var settings = await _unitOfWork.SettingsRepository.GetSettingsDtoAsync();
|
||||
@ -190,6 +181,7 @@ public class ImageController : BaseApiController
|
||||
if (_directoryService.FileSystem.File.Exists(destFile)) return destFile;
|
||||
ImageService.CreateMergedImage(
|
||||
covers.Select(c => _directoryService.FileSystem.Path.Join(_directoryService.CoverImageDirectory, c)).ToList(),
|
||||
settings.CoverImageSize,
|
||||
destFile);
|
||||
return !_directoryService.FileSystem.File.Exists(destFile) ? string.Empty : destFile;
|
||||
}
|
||||
|
@ -197,6 +197,12 @@ public class SettingsController : BaseApiController
|
||||
_unitOfWork.SettingsRepository.Update(setting);
|
||||
}
|
||||
|
||||
if (setting.Key == ServerSettingKey.CoverImageSize && updateSettingsDto.CoverImageSize + string.Empty != setting.Value)
|
||||
{
|
||||
setting.Value = updateSettingsDto.CoverImageSize + string.Empty;
|
||||
_unitOfWork.SettingsRepository.Update(setting);
|
||||
}
|
||||
|
||||
if (setting.Key == ServerSettingKey.TaskScan && updateSettingsDto.TaskScan != setting.Value)
|
||||
{
|
||||
setting.Value = updateSettingsDto.TaskScan;
|
||||
|
@ -84,4 +84,8 @@ public class ServerSettingDto
|
||||
/// How many Days since today in the past for chapter updates, should content be considered for On Deck, before it gets removed automatically
|
||||
/// </summary>
|
||||
public int OnDeckUpdateDays { get; set; }
|
||||
/// <summary>
|
||||
/// How large the cover images should be
|
||||
/// </summary>
|
||||
public CoverImageSize CoverImageSize { get; set; }
|
||||
}
|
||||
|
@ -127,7 +127,6 @@ public class CollectionTagRepository : ICollectionTagRepository
|
||||
.Select(sm => sm.Series.CoverImage)
|
||||
.Where(t => !string.IsNullOrEmpty(t))
|
||||
.ToListAsync();
|
||||
if (data.Count < 4) return new List<string>();
|
||||
return data
|
||||
.OrderBy(_ => random.Next())
|
||||
.Take(4)
|
||||
|
@ -101,7 +101,6 @@ public class ReadingListRepository : IReadingListRepository
|
||||
.SelectMany(r => r.Items.Select(ri => ri.Chapter.CoverImage))
|
||||
.Where(t => !string.IsNullOrEmpty(t))
|
||||
.ToListAsync();
|
||||
if (data.Count < 4) return new List<string>();
|
||||
return data
|
||||
.OrderBy(_ => random.Next())
|
||||
.Take(4)
|
||||
|
@ -108,8 +108,9 @@ public static class Seed
|
||||
new() {Key = ServerSettingKey.HostName, Value = string.Empty},
|
||||
new() {Key = ServerSettingKey.EncodeMediaAs, Value = EncodeFormat.PNG.ToString()},
|
||||
new() {Key = ServerSettingKey.LicenseKey, Value = string.Empty},
|
||||
new() {Key = ServerSettingKey.OnDeckProgressDays, Value = $"{30}"},
|
||||
new() {Key = ServerSettingKey.OnDeckUpdateDays, Value = $"{7}"},
|
||||
new() {Key = ServerSettingKey.OnDeckProgressDays, Value = "30"},
|
||||
new() {Key = ServerSettingKey.OnDeckUpdateDays, Value = "7"},
|
||||
new() {Key = ServerSettingKey.CoverImageSize, Value = CoverImageSize.Default.ToString()},
|
||||
new() {
|
||||
Key = ServerSettingKey.CacheSize, Value = Configuration.DefaultCacheMemory + string.Empty
|
||||
}, // Not used from DB, but DB is sync with appSettings.json
|
||||
|
36
API/Entities/Enums/CoverImageSize.cs
Normal file
36
API/Entities/Enums/CoverImageSize.cs
Normal file
@ -0,0 +1,36 @@
|
||||
namespace API.Entities.Enums;
|
||||
|
||||
public enum CoverImageSize
|
||||
{
|
||||
/// <summary>
|
||||
/// Default Size: 320x455 (wxh)
|
||||
/// </summary>
|
||||
Default = 1,
|
||||
/// <summary>
|
||||
/// 640x909
|
||||
/// </summary>
|
||||
Medium = 2,
|
||||
/// <summary>
|
||||
/// 900x1277
|
||||
/// </summary>
|
||||
Large = 3,
|
||||
/// <summary>
|
||||
/// 1265x1795
|
||||
/// </summary>
|
||||
XLarge = 4
|
||||
}
|
||||
|
||||
public static class CoverImageSizeExtensions
|
||||
{
|
||||
public static (int Width, int Height) GetDimensions(this CoverImageSize size)
|
||||
{
|
||||
return size switch
|
||||
{
|
||||
CoverImageSize.Default => (320, 455),
|
||||
CoverImageSize.Medium => (640, 909),
|
||||
CoverImageSize.Large => (900, 1277),
|
||||
CoverImageSize.XLarge => (1265, 1795),
|
||||
_ => (320, 455)
|
||||
};
|
||||
}
|
||||
}
|
@ -143,5 +143,10 @@ public enum ServerSettingKey
|
||||
/// </summary>
|
||||
[Description("OnDeckUpdateDays")]
|
||||
OnDeckUpdateDays = 26,
|
||||
/// <summary>
|
||||
/// The size of the cover image thumbnail. Defaults to <see cref="CoverImageSize"/>.Default
|
||||
/// </summary>
|
||||
[Description("CoverImageSize")]
|
||||
CoverImageSize = 27
|
||||
|
||||
}
|
||||
|
@ -79,6 +79,9 @@ public class ServerSettingConverter : ITypeConverter<IEnumerable<ServerSetting>,
|
||||
case ServerSettingKey.OnDeckUpdateDays:
|
||||
destination.OnDeckUpdateDays = int.Parse(row.Value);
|
||||
break;
|
||||
case ServerSettingKey.CoverImageSize:
|
||||
destination.CoverImageSize = Enum.Parse<CoverImageSize>(row.Value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,7 @@ public interface IArchiveService
|
||||
{
|
||||
void ExtractArchive(string archivePath, string extractPath);
|
||||
int GetNumberOfPagesFromArchive(string archivePath);
|
||||
string GetCoverImage(string archivePath, string fileName, string outputDirectory, EncodeFormat format);
|
||||
string GetCoverImage(string archivePath, string fileName, string outputDirectory, EncodeFormat format, CoverImageSize size = CoverImageSize.Default);
|
||||
bool IsValidArchive(string archivePath);
|
||||
ComicInfo? GetComicInfo(string archivePath);
|
||||
ArchiveLibrary CanOpen(string archivePath);
|
||||
@ -205,7 +205,7 @@ public class ArchiveService : IArchiveService
|
||||
/// <param name="outputDirectory">Where to output the file, defaults to covers directory</param>
|
||||
/// <param name="format">When saving the file, use encoding</param>
|
||||
/// <returns></returns>
|
||||
public string GetCoverImage(string archivePath, string fileName, string outputDirectory, EncodeFormat format)
|
||||
public string GetCoverImage(string archivePath, string fileName, string outputDirectory, EncodeFormat format, CoverImageSize size = CoverImageSize.Default)
|
||||
{
|
||||
if (archivePath == null || !IsValidArchive(archivePath)) return string.Empty;
|
||||
try
|
||||
@ -221,7 +221,7 @@ public class ArchiveService : IArchiveService
|
||||
var entry = archive.Entries.Single(e => e.FullName == entryName);
|
||||
|
||||
using var stream = entry.Open();
|
||||
return _imageService.WriteCoverThumbnail(stream, fileName, outputDirectory, format);
|
||||
return _imageService.WriteCoverThumbnail(stream, fileName, outputDirectory, format, size);
|
||||
}
|
||||
case ArchiveLibrary.SharpCompress:
|
||||
{
|
||||
@ -232,7 +232,7 @@ public class ArchiveService : IArchiveService
|
||||
var entry = archive.Entries.Single(e => e.Key == entryName);
|
||||
|
||||
using var stream = entry.OpenEntryStream();
|
||||
return _imageService.WriteCoverThumbnail(stream, fileName, outputDirectory, format);
|
||||
return _imageService.WriteCoverThumbnail(stream, fileName, outputDirectory, format, size);
|
||||
}
|
||||
case ArchiveLibrary.NotSupported:
|
||||
_logger.LogWarning("[GetCoverImage] This archive cannot be read: {ArchivePath}. Defaulting to no cover image", archivePath);
|
||||
@ -246,7 +246,7 @@ public class ArchiveService : IArchiveService
|
||||
{
|
||||
_logger.LogWarning(ex, "[GetCoverImage] There was an exception when reading archive stream: {ArchivePath}. Defaulting to no cover image", archivePath);
|
||||
_mediaErrorService.ReportMediaIssue(archivePath, MediaErrorProducer.ArchiveService,
|
||||
"This archive cannot be read or not supported", ex);
|
||||
"This archive cannot be read or not supported", ex); // TODO: Localize this
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
|
@ -33,7 +33,7 @@ namespace API.Services;
|
||||
public interface IBookService
|
||||
{
|
||||
int GetNumberOfPages(string filePath);
|
||||
string GetCoverImage(string fileFilePath, string fileName, string outputDirectory, EncodeFormat encodeFormat);
|
||||
string GetCoverImage(string fileFilePath, string fileName, string outputDirectory, EncodeFormat encodeFormat, CoverImageSize size = CoverImageSize.Default);
|
||||
ComicInfo? GetComicInfo(string filePath);
|
||||
ParserInfo? ParseInfo(string filePath);
|
||||
/// <summary>
|
||||
@ -1196,13 +1196,13 @@ public class BookService : IBookService
|
||||
/// <param name="outputDirectory">Where to output the file, defaults to covers directory</param>
|
||||
/// <param name="encodeFormat">When saving the file, use encoding</param>
|
||||
/// <returns></returns>
|
||||
public string GetCoverImage(string fileFilePath, string fileName, string outputDirectory, EncodeFormat encodeFormat)
|
||||
public string GetCoverImage(string fileFilePath, string fileName, string outputDirectory, EncodeFormat encodeFormat, CoverImageSize size = CoverImageSize.Default)
|
||||
{
|
||||
if (!IsValidFile(fileFilePath)) return string.Empty;
|
||||
|
||||
if (Parser.IsPdf(fileFilePath))
|
||||
{
|
||||
return GetPdfCoverImage(fileFilePath, fileName, outputDirectory, encodeFormat);
|
||||
return GetPdfCoverImage(fileFilePath, fileName, outputDirectory, encodeFormat, size);
|
||||
}
|
||||
|
||||
using var epubBook = EpubReader.OpenBook(fileFilePath, BookReaderOptions);
|
||||
@ -1217,20 +1217,20 @@ public class BookService : IBookService
|
||||
if (coverImageContent == null) return string.Empty;
|
||||
using var stream = coverImageContent.GetContentStream();
|
||||
|
||||
return _imageService.WriteCoverThumbnail(stream, fileName, outputDirectory, encodeFormat);
|
||||
return _imageService.WriteCoverThumbnail(stream, fileName, outputDirectory, encodeFormat, size);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "[BookService] There was a critical error and prevented thumbnail generation on {BookFile}. Defaulting to no cover image", fileFilePath);
|
||||
_mediaErrorService.ReportMediaIssue(fileFilePath, MediaErrorProducer.BookService,
|
||||
"There was a critical error and prevented thumbnail generation", ex);
|
||||
"There was a critical error and prevented thumbnail generation", ex); // TODO: Localize this
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
|
||||
private string GetPdfCoverImage(string fileFilePath, string fileName, string outputDirectory, EncodeFormat encodeFormat)
|
||||
private string GetPdfCoverImage(string fileFilePath, string fileName, string outputDirectory, EncodeFormat encodeFormat, CoverImageSize size)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -1240,7 +1240,7 @@ public class BookService : IBookService
|
||||
using var stream = StreamManager.GetStream("BookService.GetPdfPage");
|
||||
GetPdfPage(docReader, 0, stream);
|
||||
|
||||
return _imageService.WriteCoverThumbnail(stream, fileName, outputDirectory, encodeFormat);
|
||||
return _imageService.WriteCoverThumbnail(stream, fileName, outputDirectory, encodeFormat, size);
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -21,7 +21,7 @@ namespace API.Services;
|
||||
public interface IImageService
|
||||
{
|
||||
void ExtractImages(string fileFilePath, string targetDirectory, int fileCount = 1);
|
||||
string GetCoverImage(string path, string fileName, string outputDirectory, EncodeFormat encodeFormat);
|
||||
string GetCoverImage(string path, string fileName, string outputDirectory, EncodeFormat encodeFormat, CoverImageSize size);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Thumbnail version of a base64 image
|
||||
@ -40,7 +40,7 @@ public interface IImageService
|
||||
/// <param name="outputDirectory"></param>
|
||||
/// <param name="encodeFormat"></param>
|
||||
/// <returns></returns>
|
||||
string WriteCoverThumbnail(Stream stream, string fileName, string outputDirectory, EncodeFormat encodeFormat);
|
||||
string WriteCoverThumbnail(Stream stream, string fileName, string outputDirectory, EncodeFormat encodeFormat, CoverImageSize size = CoverImageSize.Default);
|
||||
/// <summary>
|
||||
/// Writes out a thumbnail by file path input
|
||||
/// </summary>
|
||||
@ -49,7 +49,7 @@ public interface IImageService
|
||||
/// <param name="outputDirectory"></param>
|
||||
/// <param name="encodeFormat"></param>
|
||||
/// <returns></returns>
|
||||
string WriteCoverThumbnail(string sourceFile, string fileName, string outputDirectory, EncodeFormat encodeFormat);
|
||||
string WriteCoverThumbnail(string sourceFile, string fileName, string outputDirectory, EncodeFormat encodeFormat, CoverImageSize size = CoverImageSize.Default);
|
||||
/// <summary>
|
||||
/// Converts the passed image to encoding and outputs it in the same directory
|
||||
/// </summary>
|
||||
@ -87,6 +87,7 @@ public class ImageService : IImageService
|
||||
/// </summary>
|
||||
public const int LibraryThumbnailWidth = 32;
|
||||
|
||||
|
||||
private static readonly string[] ValidIconRelations = {
|
||||
"icon",
|
||||
"apple-touch-icon",
|
||||
@ -124,13 +125,14 @@ public class ImageService : IImageService
|
||||
}
|
||||
}
|
||||
|
||||
public string GetCoverImage(string path, string fileName, string outputDirectory, EncodeFormat encodeFormat)
|
||||
public string GetCoverImage(string path, string fileName, string outputDirectory, EncodeFormat encodeFormat, CoverImageSize size)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path)) return string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
using var thumbnail = Image.Thumbnail(path, ThumbnailWidth, height: ThumbnailHeight, size: Enums.Size.Force);
|
||||
var dims = size.GetDimensions();
|
||||
using var thumbnail = Image.Thumbnail(path, dims.Width, height: dims.Height, size: Enums.Size.Force);
|
||||
var filename = fileName + encodeFormat.GetExtension();
|
||||
thumbnail.WriteToFile(_directoryService.FileSystem.Path.Join(outputDirectory, filename));
|
||||
return filename;
|
||||
@ -152,9 +154,10 @@ public class ImageService : IImageService
|
||||
/// <param name="outputDirectory">Where to output the file, defaults to covers directory</param>
|
||||
/// <param name="encodeFormat">Export the file as the passed encoding</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, EncodeFormat encodeFormat)
|
||||
public string WriteCoverThumbnail(Stream stream, string fileName, string outputDirectory, EncodeFormat encodeFormat, CoverImageSize size = CoverImageSize.Default)
|
||||
{
|
||||
using var thumbnail = Image.ThumbnailStream(stream, ThumbnailWidth, height: ThumbnailHeight, size: Enums.Size.Force);
|
||||
var dims = size.GetDimensions();
|
||||
using var thumbnail = Image.ThumbnailStream(stream, dims.Width, height: dims.Height, size: Enums.Size.Force);
|
||||
var filename = fileName + encodeFormat.GetExtension();
|
||||
_directoryService.ExistOrCreate(outputDirectory);
|
||||
try
|
||||
@ -165,9 +168,10 @@ public class ImageService : IImageService
|
||||
return filename;
|
||||
}
|
||||
|
||||
public string WriteCoverThumbnail(string sourceFile, string fileName, string outputDirectory, EncodeFormat encodeFormat)
|
||||
public string WriteCoverThumbnail(string sourceFile, string fileName, string outputDirectory, EncodeFormat encodeFormat, CoverImageSize size = CoverImageSize.Default)
|
||||
{
|
||||
using var thumbnail = Image.Thumbnail(sourceFile, ThumbnailWidth, height: ThumbnailHeight, size: Enums.Size.Force);
|
||||
var dims = size.GetDimensions();
|
||||
using var thumbnail = Image.Thumbnail(sourceFile, dims.Width, height: dims.Height, size: Enums.Size.Force);
|
||||
var filename = fileName + encodeFormat.GetExtension();
|
||||
_directoryService.ExistOrCreate(outputDirectory);
|
||||
try
|
||||
@ -420,27 +424,58 @@ public class ImageService : IImageService
|
||||
}
|
||||
|
||||
|
||||
public static string CreateMergedImage(IList<string> coverImages, string dest)
|
||||
public static void CreateMergedImage(IList<string> coverImages, CoverImageSize size, string dest)
|
||||
{
|
||||
var image = Image.Black(ThumbnailWidth, ThumbnailHeight); // 320x455
|
||||
var dims = size.GetDimensions();
|
||||
int rows, cols;
|
||||
|
||||
var thumbnailWidth = image.Width / 2;
|
||||
var thumbnailHeight = image.Height / 2;
|
||||
if (coverImages.Count == 1)
|
||||
{
|
||||
rows = 1;
|
||||
cols = 1;
|
||||
}
|
||||
else if (coverImages.Count == 2)
|
||||
{
|
||||
rows = 1;
|
||||
cols = 2;
|
||||
}
|
||||
else if (coverImages.Count == 3)
|
||||
{
|
||||
rows = 2;
|
||||
cols = 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Default to 2x2 layout for more than 3 images
|
||||
rows = 2;
|
||||
cols = 2;
|
||||
}
|
||||
|
||||
var image = Image.Black(dims.Width, dims.Height);
|
||||
|
||||
var thumbnailWidth = image.Width / cols;
|
||||
var thumbnailHeight = image.Height / rows;
|
||||
|
||||
for (var i = 0; i < coverImages.Count; i++)
|
||||
{
|
||||
var tile = Image.NewFromFile(coverImages[i], access: Enums.Access.Sequential);
|
||||
|
||||
// Resize the tile to fit the thumbnail size
|
||||
tile = tile.ThumbnailImage(thumbnailWidth, height: thumbnailHeight);
|
||||
|
||||
var x = (i % 2) * thumbnailWidth;
|
||||
var y = (i / 2) * thumbnailHeight;
|
||||
var row = i / cols;
|
||||
var col = i % cols;
|
||||
|
||||
var x = col * thumbnailWidth;
|
||||
var y = row * thumbnailHeight;
|
||||
|
||||
if (coverImages.Count == 3 && i == 2)
|
||||
{
|
||||
x = (image.Width - thumbnailWidth) / 2;
|
||||
y = thumbnailHeight;
|
||||
}
|
||||
|
||||
image = image.Insert(tile, x, y);
|
||||
}
|
||||
|
||||
image.WriteToFile(dest);
|
||||
return dest;
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ public class MediaErrorService : IMediaErrorService
|
||||
|
||||
public void ReportMediaIssue(string filename, MediaErrorProducer producer, string errorMessage, Exception ex)
|
||||
{
|
||||
// TODO: Localize all these messages
|
||||
// To avoid overhead on commits, do async. We don't need to wait.
|
||||
BackgroundJob.Enqueue(() => ReportMediaIssueAsync(filename, producer, errorMessage, ex.Message));
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ public interface IMetadataService
|
||||
/// <param name="forceUpdate">Overrides any cache logic and forces execution</param>
|
||||
|
||||
Task GenerateCoversForSeries(int libraryId, int seriesId, bool forceUpdate = true);
|
||||
Task GenerateCoversForSeries(Series series, EncodeFormat encodeFormat, bool forceUpdate = false);
|
||||
Task GenerateCoversForSeries(Series series, EncodeFormat encodeFormat, CoverImageSize coverImageSize, bool forceUpdate = false);
|
||||
Task RemoveAbandonedMetadataKeys();
|
||||
}
|
||||
|
||||
@ -65,7 +65,7 @@ public class MetadataService : IMetadataService
|
||||
/// <param name="chapter"></param>
|
||||
/// <param name="forceUpdate">Force updating cover image even if underlying file has not been modified or chapter already has a cover image</param>
|
||||
/// <param name="encodeFormat">Convert image to Encoding Format when extracting the cover</param>
|
||||
private Task<bool> UpdateChapterCoverImage(Chapter chapter, bool forceUpdate, EncodeFormat encodeFormat)
|
||||
private Task<bool> UpdateChapterCoverImage(Chapter chapter, bool forceUpdate, EncodeFormat encodeFormat, CoverImageSize coverImageSize)
|
||||
{
|
||||
var firstFile = chapter.Files.MinBy(x => x.Chapter);
|
||||
if (firstFile == null) return Task.FromResult(false);
|
||||
@ -79,7 +79,7 @@ public class MetadataService : IMetadataService
|
||||
_logger.LogDebug("[MetadataService] Generating cover image for {File}", firstFile.FilePath);
|
||||
|
||||
chapter.CoverImage = _readingItemService.GetCoverImage(firstFile.FilePath,
|
||||
ImageService.GetChapterFormat(chapter.Id, chapter.VolumeId), firstFile.Format, encodeFormat);
|
||||
ImageService.GetChapterFormat(chapter.Id, chapter.VolumeId), firstFile.Format, encodeFormat, coverImageSize);
|
||||
_unitOfWork.ChapterRepository.Update(chapter);
|
||||
_updateEvents.Add(MessageFactory.CoverUpdateEvent(chapter.Id, MessageFactoryEntityTypes.Chapter));
|
||||
return Task.FromResult(true);
|
||||
@ -143,7 +143,7 @@ public class MetadataService : IMetadataService
|
||||
/// <param name="series"></param>
|
||||
/// <param name="forceUpdate"></param>
|
||||
/// <param name="encodeFormat"></param>
|
||||
private async Task ProcessSeriesCoverGen(Series series, bool forceUpdate, EncodeFormat encodeFormat)
|
||||
private async Task ProcessSeriesCoverGen(Series series, bool forceUpdate, EncodeFormat encodeFormat, CoverImageSize coverImageSize)
|
||||
{
|
||||
_logger.LogDebug("[MetadataService] Processing cover image generation for series: {SeriesName}", series.OriginalName);
|
||||
try
|
||||
@ -156,7 +156,7 @@ public class MetadataService : IMetadataService
|
||||
var index = 0;
|
||||
foreach (var chapter in volume.Chapters)
|
||||
{
|
||||
var chapterUpdated = await UpdateChapterCoverImage(chapter, forceUpdate, encodeFormat);
|
||||
var chapterUpdated = await UpdateChapterCoverImage(chapter, forceUpdate, encodeFormat, coverImageSize);
|
||||
// If cover was update, either the file has changed or first scan and we should force a metadata update
|
||||
UpdateChapterLastModified(chapter, forceUpdate || chapterUpdated);
|
||||
if (index == 0 && chapterUpdated)
|
||||
@ -208,7 +208,9 @@ public class MetadataService : IMetadataService
|
||||
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress,
|
||||
MessageFactory.CoverUpdateProgressEvent(library.Id, 0F, ProgressEventType.Started, $"Starting {library.Name}"));
|
||||
|
||||
var encodeFormat = (await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EncodeMediaAs;
|
||||
var settings = await _unitOfWork.SettingsRepository.GetSettingsDtoAsync();
|
||||
var encodeFormat = settings.EncodeMediaAs;
|
||||
var coverImageSize = settings.CoverImageSize;
|
||||
|
||||
for (var chunk = 1; chunk <= chunkInfo.TotalChunks; chunk++)
|
||||
{
|
||||
@ -238,7 +240,7 @@ public class MetadataService : IMetadataService
|
||||
|
||||
try
|
||||
{
|
||||
await ProcessSeriesCoverGen(series, forceUpdate, encodeFormat);
|
||||
await ProcessSeriesCoverGen(series, forceUpdate, encodeFormat, coverImageSize);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -288,8 +290,10 @@ public class MetadataService : IMetadataService
|
||||
return;
|
||||
}
|
||||
|
||||
var encodeFormat = (await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EncodeMediaAs;
|
||||
await GenerateCoversForSeries(series, encodeFormat, forceUpdate);
|
||||
var settings = await _unitOfWork.SettingsRepository.GetSettingsDtoAsync();
|
||||
var encodeFormat = settings.EncodeMediaAs;
|
||||
var coverImageSize = settings.CoverImageSize;
|
||||
await GenerateCoversForSeries(series, encodeFormat, coverImageSize, forceUpdate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -298,13 +302,13 @@ public class MetadataService : IMetadataService
|
||||
/// <param name="series">A full Series, with metadata, chapters, etc</param>
|
||||
/// <param name="encodeFormat">When saving the file, what encoding should be used</param>
|
||||
/// <param name="forceUpdate"></param>
|
||||
public async Task GenerateCoversForSeries(Series series, EncodeFormat encodeFormat, bool forceUpdate = false)
|
||||
public async Task GenerateCoversForSeries(Series series, EncodeFormat encodeFormat, CoverImageSize coverImageSize, bool forceUpdate = false)
|
||||
{
|
||||
var sw = Stopwatch.StartNew();
|
||||
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress,
|
||||
MessageFactory.CoverUpdateProgressEvent(series.LibraryId, 0F, ProgressEventType.Started, series.Name));
|
||||
|
||||
await ProcessSeriesCoverGen(series, forceUpdate, encodeFormat);
|
||||
await ProcessSeriesCoverGen(series, forceUpdate, encodeFormat, coverImageSize);
|
||||
|
||||
|
||||
if (_unitOfWork.HasChanges())
|
||||
|
@ -10,7 +10,7 @@ public interface IReadingItemService
|
||||
{
|
||||
ComicInfo? GetComicInfo(string filePath);
|
||||
int GetNumberOfPages(string filePath, MangaFormat format);
|
||||
string GetCoverImage(string filePath, string fileName, MangaFormat format, EncodeFormat encodeFormat);
|
||||
string GetCoverImage(string filePath, string fileName, MangaFormat format, EncodeFormat encodeFormat, CoverImageSize size = CoverImageSize.Default);
|
||||
void Extract(string fileFilePath, string targetDirectory, MangaFormat format, int imageCount = 1);
|
||||
ParserInfo? ParseFile(string path, string rootPath, LibraryType type);
|
||||
}
|
||||
@ -162,7 +162,7 @@ public class ReadingItemService : IReadingItemService
|
||||
}
|
||||
}
|
||||
|
||||
public string GetCoverImage(string filePath, string fileName, MangaFormat format, EncodeFormat encodeFormat)
|
||||
public string GetCoverImage(string filePath, string fileName, MangaFormat format, EncodeFormat encodeFormat, CoverImageSize size = CoverImageSize.Default)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filePath) || string.IsNullOrEmpty(fileName))
|
||||
{
|
||||
@ -172,10 +172,10 @@ public class ReadingItemService : IReadingItemService
|
||||
|
||||
return format switch
|
||||
{
|
||||
MangaFormat.Epub => _bookService.GetCoverImage(filePath, fileName, _directoryService.CoverImageDirectory, encodeFormat),
|
||||
MangaFormat.Archive => _archiveService.GetCoverImage(filePath, fileName, _directoryService.CoverImageDirectory, encodeFormat),
|
||||
MangaFormat.Image => _imageService.GetCoverImage(filePath, fileName, _directoryService.CoverImageDirectory, encodeFormat),
|
||||
MangaFormat.Pdf => _bookService.GetCoverImage(filePath, fileName, _directoryService.CoverImageDirectory, encodeFormat),
|
||||
MangaFormat.Epub => _bookService.GetCoverImage(filePath, fileName, _directoryService.CoverImageDirectory, encodeFormat, size),
|
||||
MangaFormat.Archive => _archiveService.GetCoverImage(filePath, fileName, _directoryService.CoverImageDirectory, encodeFormat, size),
|
||||
MangaFormat.Image => _imageService.GetCoverImage(filePath, fileName, _directoryService.CoverImageDirectory, encodeFormat, size),
|
||||
MangaFormat.Pdf => _bookService.GetCoverImage(filePath, fileName, _directoryService.CoverImageDirectory, encodeFormat, size),
|
||||
_ => string.Empty
|
||||
};
|
||||
}
|
||||
|
@ -231,7 +231,8 @@ public class ProcessSeries : IProcessSeries
|
||||
_logger.LogError(ex, "[ScannerService] There was an exception updating series for {SeriesName}", series.Name);
|
||||
}
|
||||
|
||||
await _metadataService.GenerateCoversForSeries(series, (await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EncodeMediaAs);
|
||||
var settings = await _unitOfWork.SettingsRepository.GetSettingsDtoAsync();
|
||||
await _metadataService.GenerateCoversForSeries(series, settings.EncodeMediaAs, settings.CoverImageSize);
|
||||
EnqueuePostSeriesProcessTasks(series.LibraryId, series.Id);
|
||||
}
|
||||
|
||||
|
14
UI/Web/src/app/admin/_models/cover-image-size.ts
Normal file
14
UI/Web/src/app/admin/_models/cover-image-size.ts
Normal file
@ -0,0 +1,14 @@
|
||||
export enum CoverImageSize {
|
||||
Default = 1,
|
||||
Medium = 2,
|
||||
Large = 3,
|
||||
XLarge = 4
|
||||
}
|
||||
|
||||
export const CoverImageSizes =
|
||||
[
|
||||
{value: CoverImageSize.Default, title: 'cover-image-size.default'},
|
||||
{value: CoverImageSize.Medium, title: 'cover-image-size.medium'},
|
||||
{value: CoverImageSize.Large, title: 'cover-image-size.large'},
|
||||
{value: CoverImageSize.XLarge, title: 'cover-image-size.xlarge'}
|
||||
];
|
@ -1,4 +1,5 @@
|
||||
import { EncodeFormat } from "./encode-format";
|
||||
import {CoverImageSize} from "./cover-image-size";
|
||||
|
||||
export interface ServerSettings {
|
||||
cacheDirectory: string;
|
||||
@ -20,4 +21,5 @@ export interface ServerSettings {
|
||||
cacheSize: number;
|
||||
onDeckProgressDays: number;
|
||||
onDeckUpdateDays: number;
|
||||
coverImageSize: CoverImageSize;
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
<br/><b>{{t('encode-as-warning')}}</b>
|
||||
</p>
|
||||
<div *ngIf="settingsForm.get('encodeMediaAs')?.dirty" class="alert alert-danger" role="alert">{{t('media-warning')}}</div>
|
||||
<div class="col-md-6 col-sm-12 mb-3">
|
||||
<div class="col-md-6 col-sm-12 mb-3 pe-1">
|
||||
<label for="settings-media-encodeMediaAs" class="form-label me-1">{{t('encode-as-label')}}</label>
|
||||
<i class="fa fa-info-circle" placement="right" [ngbTooltip]="encodeMediaAsTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #encodeMediaAsTooltip>{{t('encode-as-tooltip')}}</ng-template>
|
||||
@ -16,6 +16,16 @@
|
||||
<option *ngFor="let format of EncodeFormats" [value]="format.value">{{format.title}}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 col-sm-12 mb-3">
|
||||
<label for="settings-media-coverImageSize" class="form-label me-1">{{t('cover-image-size-label')}}</label>
|
||||
<i class="fa fa-info-circle" placement="right" [ngbTooltip]="coverImageSizeTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #coverImageSizeTooltip>{{t('cover-image-size-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-media-coverImageSize-help"><ng-container [ngTemplateOutlet]="coverImageSizeTooltip"></ng-container></span>
|
||||
<select class="form-select" aria-describedby="settings-media-coverImageSize-help" formControlName="coverImageSize" id="settings-media-coverImageSize">
|
||||
<option *ngFor="let size of coverImageSizes" [value]="size.value">{{size.title}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0">
|
||||
|
@ -21,7 +21,8 @@ import {EncodeFormats} from '../_models/encode-format';
|
||||
import {ManageScrobbleErrorsComponent} from '../manage-scrobble-errors/manage-scrobble-errors.component';
|
||||
import {ManageAlertsComponent} from '../manage-alerts/manage-alerts.component';
|
||||
import {NgFor, NgIf, NgTemplateOutlet} from '@angular/common';
|
||||
import {TranslocoDirective, TranslocoService} from "@ngneat/transloco";
|
||||
import {translate, TranslocoDirective, TranslocoService} from "@ngneat/transloco";
|
||||
import { CoverImageSizes } from '../_models/cover-image-size';
|
||||
|
||||
@Component({
|
||||
selector: 'app-manage-media-settings',
|
||||
@ -38,6 +39,11 @@ export class ManageMediaSettingsComponent implements OnInit {
|
||||
|
||||
alertCount: number = 0;
|
||||
scrobbleCount: number = 0;
|
||||
coverImageSizes = CoverImageSizes.map(o => {
|
||||
const newObj = {...o};
|
||||
newObj.title = translate(o.title);
|
||||
return newObj;
|
||||
})
|
||||
|
||||
private readonly translocoService = inject(TranslocoService);
|
||||
private readonly cdRef = inject(ChangeDetectorRef);
|
||||
@ -51,6 +57,7 @@ export class ManageMediaSettingsComponent implements OnInit {
|
||||
this.serverSettings = settings;
|
||||
this.settingsForm.addControl('encodeMediaAs', new FormControl(this.serverSettings.encodeMediaAs, [Validators.required]));
|
||||
this.settingsForm.addControl('bookmarksDirectory', new FormControl(this.serverSettings.bookmarksDirectory, [Validators.required]));
|
||||
this.settingsForm.addControl('coverImageSize', new FormControl(this.serverSettings.coverImageSize, [Validators.required]));
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
}
|
||||
@ -58,6 +65,7 @@ export class ManageMediaSettingsComponent implements OnInit {
|
||||
resetForm() {
|
||||
this.settingsForm.get('encodeMediaAs')?.setValue(this.serverSettings.encodeMediaAs);
|
||||
this.settingsForm.get('bookmarksDirectory')?.setValue(this.serverSettings.bookmarksDirectory);
|
||||
this.settingsForm.get('coverImageSize')?.setValue(this.serverSettings.coverImageSize);
|
||||
this.settingsForm.markAsPristine();
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
@ -66,6 +74,7 @@ export class ManageMediaSettingsComponent implements OnInit {
|
||||
const modelSettings = Object.assign({}, this.serverSettings);
|
||||
modelSettings.encodeMediaAs = parseInt(this.settingsForm.get('encodeMediaAs')?.value, 10);
|
||||
modelSettings.bookmarksDirectory = this.settingsForm.get('bookmarksDirectory')?.value;
|
||||
modelSettings.coverImageSize = parseInt(this.settingsForm.get('coverImageSize')?.value, 10);
|
||||
|
||||
this.settingsService.updateServerSettings(modelSettings).pipe(take(1)).subscribe(async (settings: ServerSettings) => {
|
||||
this.serverSettings = settings;
|
||||
|
@ -11,7 +11,9 @@
|
||||
[trackByIdentity]="trackByIdentity"
|
||||
>
|
||||
<ng-template #cardItem let-item let-position="idx">
|
||||
<app-card-item [title]="item.title" [entity]="item" [actions]="collectionTagActions" [imageUrl]="imageSerivce.getCollectionCoverImage(item.id)" (clicked)="loadCollection(item)"></app-card-item>
|
||||
<app-card-item [title]="item.title" [entity]="item" [actions]="collectionTagActions"
|
||||
[imageUrl]="imageSerivce.getCollectionCoverImage(item.id)"
|
||||
(clicked)="loadCollection(item)"></app-card-item>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #noData>
|
||||
|
@ -11,9 +11,9 @@
|
||||
</div>
|
||||
|
||||
<div [ngStyle]="{'height': ScrollingBlockHeight}" class="main-container container-fluid pt-2" *ngIf="collectionTag !== undefined" #scrollingBlock>
|
||||
<div class="row mb-3" *ngIf="(collectionTag.coverImage !== '' && collectionTag.coverImage !== undefined && collectionTag.coverImage !== null) || summary.length > 0">
|
||||
<div class="col-md-2 col-xs-4 col-sm-6 d-none d-sm-block" *ngIf="collectionTag.coverImage !== '' && collectionTag.coverImage !== undefined && collectionTag.coverImage !== null">
|
||||
<app-image maxWidth="481px" [imageUrl]="tagImage"></app-image>
|
||||
<div class="row mb-3" *ngIf="summary.length > 0">
|
||||
<div class="col-md-2 col-xs-4 col-sm-6 d-none d-sm-block">
|
||||
<app-image maxWidth="481px" [imageUrl]="imageService.getCollectionCoverImage(collectionTag.id)"></app-image>
|
||||
</div>
|
||||
<div class="col-md-10 col-xs-8 col-sm-6 mt-2">
|
||||
<app-read-more [text]="summary" [maxLength]="250"></app-read-more>
|
||||
|
@ -70,7 +70,6 @@ export class CollectionDetailComponent implements OnInit, AfterContentChecked {
|
||||
translocoService = inject(TranslocoService);
|
||||
|
||||
collectionTag!: CollectionTag;
|
||||
tagImage: string = '';
|
||||
isLoading: boolean = true;
|
||||
series: Array<Series> = [];
|
||||
pagination!: Pagination;
|
||||
@ -229,9 +228,6 @@ export class CollectionDetailComponent implements OnInit, AfterContentChecked {
|
||||
|
||||
this.collectionTag = matchingTags[0];
|
||||
this.summary = (this.collectionTag.summary === null ? '' : this.collectionTag.summary).replace(/\n/g, '<br>');
|
||||
// TODO: This can be changed now that we have app-image and collection cover merge code (can it? Because we still have the case where there is no image)
|
||||
// I can always tweak merge to allow blank slots and if just one item, just show that item image
|
||||
this.tagImage = this.imageService.randomize(this.imageService.getCollectionCoverImage(this.collectionTag.id));
|
||||
this.titleService.setTitle(this.translocoService.translate('collection-detail.title-alt', {collectionName: this.collectionTag.title}));
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
@ -285,11 +281,6 @@ export class CollectionDetailComponent implements OnInit, AfterContentChecked {
|
||||
modalRef.closed.subscribe((results: {success: boolean, coverImageUpdated: boolean}) => {
|
||||
this.updateTag(this.collectionTag.id);
|
||||
this.loadPage();
|
||||
if (results.coverImageUpdated) {
|
||||
this.tagImage = this.imageService.randomize(this.imageService.getCollectionCoverImage(collectionTag.id));
|
||||
this.collectionTag.coverImage = this.imageService.randomize(this.imageService.getCollectionCoverImage(collectionTag.id));
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -38,8 +38,8 @@
|
||||
<div class="container-fluid mt-2" *ngIf="readingList" >
|
||||
|
||||
<div class="row mb-2">
|
||||
<div class="col-md-2 col-xs-4 col-sm-6 d-none d-sm-block" *ngIf="readingList.coverImage !== '' && readingList.coverImage !== undefined && readingList.coverImage !== null">
|
||||
<app-image maxWidth="300px" maxHeight="400px" [imageUrl]="readingListImage"></app-image>
|
||||
<div class="col-md-2 col-xs-4 col-sm-6 d-none d-sm-block">
|
||||
<app-image maxWidth="300px" maxHeight="400px" [imageUrl]="imageService.getReadingListCoverImage(readingList.id)"></app-image>
|
||||
</div>
|
||||
<div class="col-md-10 col-xs-8 col-sm-6 mt-2">
|
||||
<div class="row g-0 mb-3">
|
||||
|
@ -62,7 +62,6 @@ export class ReadingListDetailComponent implements OnInit {
|
||||
downloadInProgress: boolean = false;
|
||||
|
||||
readingListSummary: string = '';
|
||||
readingListImage: string = '';
|
||||
|
||||
libraryTypes: {[key: number]: LibraryType} = {};
|
||||
characters$!: Observable<Person[]>;
|
||||
@ -90,7 +89,6 @@ export class ReadingListDetailComponent implements OnInit {
|
||||
}
|
||||
this.listId = parseInt(listId, 10);
|
||||
this.characters$ = this.readingListService.getCharacters(this.listId);
|
||||
this.readingListImage = this.imageService.randomize(this.imageService.getReadingListCoverImage(this.listId));
|
||||
|
||||
forkJoin([
|
||||
this.libraryService.getLibraries(),
|
||||
@ -162,7 +160,6 @@ export class ReadingListDetailComponent implements OnInit {
|
||||
this.readingListService.getReadingList(this.listId).subscribe(rl => {
|
||||
this.readingList = rl;
|
||||
this.readingListSummary = (this.readingList.summary === null ? '' : this.readingList.summary).replace(/\n/g, '<br>');
|
||||
this.readingListImage = this.imageService.randomize(this.imageService.getReadingListCoverImage(this.listId));
|
||||
this.cdRef.markForCheck();
|
||||
})
|
||||
});
|
||||
|
@ -2,7 +2,7 @@
|
||||
<app-side-nav-companion-bar>
|
||||
<h2 title>
|
||||
<app-card-actionables [actions]="globalActions" (actionHandler)="performGlobalAction($event)"></app-card-actionables>
|
||||
<span>Reading Lists</span>
|
||||
<span>{{t('title')}}</span>
|
||||
</h2>
|
||||
<h6 subtitle class="subtitle-with-actionables" *ngIf="pagination">{{t('item-count', {num: pagination.totalItems | number})}}</h6>
|
||||
</app-side-nav-companion-bar>
|
||||
@ -13,6 +13,7 @@
|
||||
[pagination]="pagination"
|
||||
[jumpBarKeys]="jumpbarKeys"
|
||||
[filteringDisabled]="true"
|
||||
[trackByIdentity]="trackByIdentity"
|
||||
>
|
||||
<ng-template #cardItem let-item let-position="idx" >
|
||||
<app-card-item [title]="item.title" [entity]="item" [actions]="actions[item.id]"
|
||||
|
@ -19,6 +19,7 @@ import { NgIf, DecimalPipe } from '@angular/common';
|
||||
import { SideNavCompanionBarComponent } from '../../../sidenav/_components/side-nav-companion-bar/side-nav-companion-bar.component';
|
||||
import {TranslocoDirective, TranslocoService} from "@ngneat/transloco";
|
||||
import {CardActionablesComponent} from "../../../_single-module/card-actionables/card-actionables.component";
|
||||
import {CollectionTag} from "../../../_models/collection-tag";
|
||||
|
||||
@Component({
|
||||
selector: 'app-reading-lists',
|
||||
@ -37,6 +38,7 @@ export class ReadingListsComponent implements OnInit {
|
||||
jumpbarKeys: Array<JumpKey> = [];
|
||||
actions: {[key: number]: Array<ActionItem<ReadingList>>} = {};
|
||||
globalActions: Array<ActionItem<any>> = [{action: Action.Import, title: 'import-cbl', children: [], requiresAdmin: true, callback: this.importCbl.bind(this)}];
|
||||
trackByIdentity = (index: number, item: ReadingList) => `${item.id}_${item.title}`;
|
||||
|
||||
translocoService = inject(TranslocoService);
|
||||
constructor(private readingListService: ReadingListService, public imageService: ImageService, private actionFactoryService: ActionFactoryService,
|
||||
|
@ -1069,7 +1069,16 @@
|
||||
"save": "{{common.save}}",
|
||||
|
||||
"media-issue-title": "Media Issues",
|
||||
"scrobble-issue-title": "Scrobble Issues"
|
||||
"scrobble-issue-title": "Scrobble Issues",
|
||||
"cover-image-size-label": "Cover Image Size",
|
||||
"cover-image-size-tooltip": "How large should cover images generate as. Note: Anything larger than the default will incur longer page load times."
|
||||
},
|
||||
|
||||
"cover-image-size": {
|
||||
"default": "Default (320x455)",
|
||||
"medium": "Medium (640x909)",
|
||||
"large": "Large (900x1277)",
|
||||
"xlarge": "Extra Large (1265x1795)"
|
||||
},
|
||||
|
||||
"manage-scrobble-errors": {
|
||||
|
13
openapi.json
13
openapi.json
@ -7,7 +7,7 @@
|
||||
"name": "GPL-3.0",
|
||||
"url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE"
|
||||
},
|
||||
"version": "0.7.7.7"
|
||||
"version": "0.7.7.9"
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
@ -16658,6 +16658,17 @@
|
||||
"type": "integer",
|
||||
"description": "How many Days since today in the past for chapter updates, should content be considered for On Deck, before it gets removed automatically",
|
||||
"format": "int32"
|
||||
},
|
||||
"coverImageSize": {
|
||||
"enum": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4
|
||||
],
|
||||
"type": "integer",
|
||||
"description": "How large the cover images should be",
|
||||
"format": "int32"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
|
Loading…
x
Reference in New Issue
Block a user