Fixed Cover Gen on PDFs (#3028)

This commit is contained in:
Joe Milazzo 2024-06-30 11:31:34 -05:00 committed by GitHub
parent 7997cd6329
commit 99c2dfb467
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 760 additions and 11 deletions

View File

@ -211,6 +211,7 @@ public class MangaParsingTests
[InlineData("Max Level Returner เล่มที่ 5", "Max Level Returner")]
[InlineData("หนึ่งความคิด นิจนิรันดร์ เล่ม 2", "หนึ่งความคิด นิจนิรันดร์")]
[InlineData("不安の種\uff0b - 01", "不安の種\uff0b")]
[InlineData("Giant Ojou-sama - Ch. 33.5 - Volume 04 Bonus Chapter", "Giant Ojou-sama")]
public void ParseSeriesTest(string filename, string expected)
{
Assert.Equal(expected, API.Services.Tasks.Scanner.Parser.Parser.ParseSeries(filename, LibraryType.Manga));

View File

@ -54,7 +54,7 @@
<ItemGroup>
<PackageReference Include="CsvHelper" Version="33.0.1" />
<PackageReference Include="MailKit" Version="4.6.0" />
<PackageReference Include="MailKit" Version="4.7.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.6">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@ -66,7 +66,7 @@
<PackageReference Include="Flurl" Version="3.0.7" />
<PackageReference Include="Flurl.Http" Version="3.2.4" />
<PackageReference Include="Hangfire" Version="1.8.14" />
<PackageReference Include="Hangfire.InMemory" Version="0.10.0" />
<PackageReference Include="Hangfire.InMemory" Version="0.10.3" />
<PackageReference Include="Hangfire.MaximumConcurrentExecutions" Version="1.1.0" />
<PackageReference Include="Hangfire.Storage.SQLite" Version="0.4.2" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.61" />
@ -95,7 +95,7 @@
<PackageReference Include="Serilog.Sinks.SignalR.Core" Version="0.1.2" />
<PackageReference Include="SharpCompress" Version="0.37.2" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.4" />
<PackageReference Include="SonarAnalyzer.CSharp" Version="9.26.0.92422">
<PackageReference Include="SonarAnalyzer.CSharp" Version="9.28.0.94264">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

@ -237,18 +237,40 @@ public class ImageService : IImageService
using var sourceImage = Image.NewFromStream(stream);
if (stream.CanSeek) stream.Position = 0;
var scalingSize = GetSizeForDimensions(sourceImage, targetWidth, targetHeight);
var scalingCrop = GetCropForDimensions(sourceImage, targetWidth, targetHeight);
using var thumbnail = sourceImage.ThumbnailImage(targetWidth, targetHeight,
size: GetSizeForDimensions(sourceImage, targetWidth, targetHeight),
crop: GetCropForDimensions(sourceImage, targetWidth, targetHeight));
size: scalingSize,
crop: scalingCrop);
var filename = fileName + encodeFormat.GetExtension();
_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;
try
{
thumbnail.WriteToFile(_directoryService.FileSystem.Path.Join(outputDirectory, filename));
return filename;
}
catch (VipsException)
{
// NetVips Issue: https://github.com/kleisauke/net-vips/issues/234
// Saving pdf covers from a stream can fail, so revert to old code
if (stream.CanSeek) stream.Position = 0;
using var thumbnail2 = Image.ThumbnailStream(stream, targetWidth, height: targetHeight,
size: scalingSize,
crop: scalingCrop);
thumbnail2.WriteToFile(_directoryService.FileSystem.Path.Join(outputDirectory, filename));
return filename;
}
}
public string WriteCoverThumbnail(string sourceFile, string fileName, string outputDirectory, EncodeFormat encodeFormat, CoverImageSize size = CoverImageSize.Default)

View File

@ -235,7 +235,7 @@ public static class Parser
// [SugoiSugoi]_NEEDLESS_Vol.2_-_Disk_The_Informant_5_[ENG].rar, Yuusha Ga Shinda! - Vol.tbd Chapter 27.001 V2 Infection ①.cbz,
// Nagasarete Airantou - Vol. 30 Ch. 187.5 - Vol.30 Omake
new Regex(
@"^(?<Series>.+?)(\s*Chapter\s*\d+)?(\s|_|\-\s)+Vol(ume)?\.?(\d+|tbd|\s\d).+?",
@"^(?<Series>.+?)(?:\s*|_|\-\s*)+(?:Ch(?:apter|\.|)\s*\d+(?:\.\d+)?(?:\s*|_|\-\s*)+)?Vol(?:ume|\.|)\s*(?:\d+|tbd)(?:\s|_|\-\s*).+",
MatchOptions, RegexTimeout),
// Ichiban_Ushiro_no_Daimaou_v04_ch34_[VISCANS].zip, VanDread-v01-c01.zip
new Regex(
@ -764,7 +764,10 @@ public static class Parser
var group = matches
.Select(match => match.Groups["Series"])
.FirstOrDefault(group => group.Success && group != Match.Empty);
if (group != null) return CleanTitle(group.Value);
if (group != null)
{
return CleanTitle(group.Value);
}
}
return string.Empty;

View File

@ -14,7 +14,7 @@
<PackageReference Include="Flurl.Http" Version="3.2.4" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="SonarAnalyzer.CSharp" Version="9.26.0.92422">
<PackageReference Include="SonarAnalyzer.CSharp" Version="9.28.0.94264">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

@ -7,7 +7,7 @@
"name": "GPL-3.0",
"url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE"
},
"version": "0.8.1.14"
"version": "0.8.1.15"
},
"servers": [
{
@ -22251,6 +22251,729 @@
"description": "Responsible for all things Want To Read"
}
]
} "object",
"properties": {
"seriesId": {
"type": "integer",
"format": "int32"
},
"body": {
"type": "string",
"nullable": true
}
},
"additionalProperties": false
},
"UpdateWantToReadDto": {
"type": "object",
"properties": {
"seriesIds": {
"type": "array",
"items": {
"type": "integer",
"format": "int32"
},
"description": "List of Series Ids that will be Added/Removed",
"nullable": true
}
},
"additionalProperties": false,
"description": "A list of Series to pass when working with Want To Read APIs"
},
"UploadFileDto": {
"required": [
"id",
"url"
],
"type": "object",
"properties": {
"id": {
"type": "integer",
"description": "Id of the Entity",
"format": "int32"
},
"url": {
"type": "string",
"description": "Base Url encoding of the file to upload from (can be null)",
"nullable": true
}
},
"additionalProperties": false
},
"UploadUrlDto": {
"required": [
"url"
],
"type": "object",
"properties": {
"url": {
"minLength": 1,
"type": "string",
"description": "External url"
}
},
"additionalProperties": false
},
"UserDto": {
"type": "object",
"properties": {
"username": {
"type": "string",
"nullable": true
},
"email": {
"type": "string",
"nullable": true
},
"token": {
"type": "string",
"nullable": true
},
"refreshToken": {
"type": "string",
"nullable": true
},
"apiKey": {
"type": "string",
"nullable": true
},
"preferences": {
"$ref": "#/components/schemas/UserPreferencesDto"
},
"ageRestriction": {
"$ref": "#/components/schemas/AgeRestrictionDto"
},
"kavitaVersion": {
"type": "string",
"nullable": true
}
},
"additionalProperties": false
},
"UserDtoICount": {
"type": "object",
"properties": {
"value": {
"$ref": "#/components/schemas/UserDto"
},
"count": {
"type": "integer",
"format": "int64"
}
},
"additionalProperties": false
},
"UserPreferencesDto": {
"required": [
"autoCloseMenu",
"backgroundColor",
"blurUnreadSummaries",
"bookReaderFontFamily",
"bookReaderFontSize",
"bookReaderImmersiveMode",
"bookReaderLayoutMode",
"bookReaderLineSpacing",
"bookReaderMargin",
"bookReaderReadingDirection",
"bookReaderTapToPaginate",
"bookReaderThemeName",
"bookReaderWritingStyle",
"collapseSeriesRelationships",
"emulateBook",
"globalPageLayoutMode",
"layoutMode",
"locale",
"noTransitions",
"pageSplitOption",
"pdfLayoutMode",
"pdfScrollMode",
"pdfSpreadMode",
"pdfTheme",
"promptForDownloadSize",
"readerMode",
"readingDirection",
"scalingOption",
"shareReviews",
"showScreenHints",
"swipeToPaginate",
"theme"
],
"type": "object",
"properties": {
"readingDirection": {
"enum": [
0,
1
],
"type": "integer",
"description": "Manga Reader Option: What direction should the next/prev page buttons go",
"format": "int32"
},
"scalingOption": {
"enum": [
0,
1,
2,
3
],
"type": "integer",
"description": "Manga Reader Option: How should the image be scaled to screen",
"format": "int32"
},
"pageSplitOption": {
"enum": [
0,
1,
2,
3
],
"type": "integer",
"description": "Manga Reader Option: Which side of a split image should we show first",
"format": "int32"
},
"readerMode": {
"enum": [
0,
1,
2
],
"type": "integer",
"description": "Manga Reader Option: How the manga reader should perform paging or reading of the file\r\n<example>\r\nWebtoon uses scrolling to page, LeftRight uses paging by clicking left/right side of reader, UpDown uses paging\r\nby clicking top/bottom sides of reader.\r\n</example>",
"format": "int32"
},
"layoutMode": {
"enum": [
1,
2,
3
],
"type": "integer",
"description": "Manga Reader Option: How many pages to display in the reader at once",
"format": "int32"
},
"emulateBook": {
"type": "boolean",
"description": "Manga Reader Option: Emulate a book by applying a shadow effect on the pages"
},
"backgroundColor": {
"minLength": 1,
"type": "string",
"description": "Manga Reader Option: Background color of the reader"
},
"swipeToPaginate": {
"type": "boolean",
"description": "Manga Reader Option: Should swiping trigger pagination"
},
"autoCloseMenu": {
"type": "boolean",
"description": "Manga Reader Option: Allow the menu to close after 6 seconds without interaction"
},
"showScreenHints": {
"type": "boolean",
"description": "Manga Reader Option: Show screen hints to the user on some actions, ie) pagination direction change"
},
"bookReaderMargin": {
"type": "integer",
"description": "Book Reader Option: Override extra Margin",
"format": "int32"
},
"bookReaderLineSpacing": {
"type": "integer",
"description": "Book Reader Option: Override line-height",
"format": "int32"
},
"bookReaderFontSize": {
"type": "integer",
"description": "Book Reader Option: Override font size",
"format": "int32"
},
"bookReaderFontFamily": {
"minLength": 1,
"type": "string",
"description": "Book Reader Option: Maps to the default Kavita font-family (inherit) or an override"
},
"bookReaderTapToPaginate": {
"type": "boolean",
"description": "Book Reader Option: Allows tapping on side of screens to paginate"
},
"bookReaderReadingDirection": {
"enum": [
0,
1
],
"type": "integer",
"description": "Book Reader Option: What direction should the next/prev page buttons go",
"format": "int32"
},
"bookReaderWritingStyle": {
"enum": [
0,
1
],
"type": "integer",
"description": "Book Reader Option: What writing style should be used, horizontal or vertical.",
"format": "int32"
},
"theme": {
"$ref": "#/components/schemas/SiteThemeDto"
},
"bookReaderThemeName": {
"minLength": 1,
"type": "string"
},
"bookReaderLayoutMode": {
"enum": [
0,
1,
2
],
"type": "integer",
"format": "int32"
},
"bookReaderImmersiveMode": {
"type": "boolean",
"description": "Book Reader Option: A flag that hides the menu-ing system behind a click on the screen. This should be used with tap to paginate, but the app doesn't enforce this."
},
"globalPageLayoutMode": {
"enum": [
0,
1
],
"type": "integer",
"description": "Global Site Option: If the UI should layout items as Cards or List items",
"format": "int32"
},
"blurUnreadSummaries": {
"type": "boolean",
"description": "UI Site Global Setting: If unread summaries should be blurred until expanded or unless user has read it already"
},
"promptForDownloadSize": {
"type": "boolean",
"description": "UI Site Global Setting: Should Kavita prompt user to confirm downloads that are greater than 100 MB."
},
"noTransitions": {
"type": "boolean",
"description": "UI Site Global Setting: Should Kavita disable CSS transitions"
},
"collapseSeriesRelationships": {
"type": "boolean",
"description": "When showing series, only parent series or series with no relationships will be returned"
},
"shareReviews": {
"type": "boolean",
"description": "UI Site Global Setting: Should series reviews be shared with all users in the server"
},
"locale": {
"minLength": 1,
"type": "string",
"description": "UI Site Global Setting: The language locale that should be used for the user"
},
"pdfTheme": {
"enum": [
0,
1
],
"type": "integer",
"description": "PDF Reader: Theme of the Reader",
"format": "int32"
},
"pdfScrollMode": {
"enum": [
0,
1,
3
],
"type": "integer",
"description": "PDF Reader: Scroll mode of the reader",
"format": "int32"
},
"pdfLayoutMode": {
"enum": [
0,
2
],
"type": "integer",
"description": "PDF Reader: Layout Mode of the reader",
"format": "int32"
},
"pdfSpreadMode": {
"enum": [
0,
1,
2
],
"type": "integer",
"description": "PDF Reader: Spread Mode of the reader",
"format": "int32"
}
},
"additionalProperties": false
},
"UserReadStatistics": {
"type": "object",
"properties": {
"totalPagesRead": {
"type": "integer",
"description": "Total number of pages read",
"format": "int64"
},
"totalWordsRead": {
"type": "integer",
"description": "Total number of words read",
"format": "int64"
},
"timeSpentReading": {
"type": "integer",
"description": "Total time spent reading based on estimates",
"format": "int64"
},
"chaptersRead": {
"type": "integer",
"format": "int64"
},
"lastActive": {
"type": "string",
"format": "date-time"
},
"avgHoursPerWeekSpentReading": {
"type": "number",
"format": "double"
},
"percentReadPerLibrary": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SingleStatCount"
},
"nullable": true
}
},
"additionalProperties": false
},
"UserReviewDto": {
"type": "object",
"properties": {
"tagline": {
"type": "string",
"description": "A tagline for the review",
"nullable": true
},
"body": {
"type": "string",
"description": "The main review",
"nullable": true
},
"bodyJustText": {
"type": "string",
"description": "The main body with just text, for review preview",
"nullable": true
},
"seriesId": {
"type": "integer",
"description": "The series this is for",
"format": "int32"
},
"libraryId": {
"type": "integer",
"description": "The library this series belongs in",
"format": "int32"
},
"username": {
"type": "string",
"description": "The user who wrote this",
"nullable": true
},
"totalVotes": {
"type": "integer",
"format": "int32"
},
"rating": {
"type": "number",
"format": "float"
},
"rawBody": {
"type": "string",
"nullable": true
},
"score": {
"type": "integer",
"description": "How many upvotes this review has gotten",
"format": "int32"
},
"siteUrl": {
"type": "string",
"description": "If External, the url of the review",
"nullable": true
},
"isExternal": {
"type": "boolean",
"description": "Does this review come from an external Source"
},
"provider": {
"enum": [
0,
1,
2
],
"type": "integer",
"description": "If this review is External, which Provider did it come from",
"format": "int32"
}
},
"additionalProperties": false,
"description": "Represents a User Review for a given Series"
},
"Volume": {
"required": [
"maxNumber",
"minNumber",
"name"
],
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int32"
},
"name": {
"type": "string",
"description": "A String representation of the volume number. Allows for floats. Can also include a range (1-2).",
"nullable": true
},
"lookupName": {
"type": "string",
"description": "This is just the original Parsed volume number for lookups",
"nullable": true
},
"number": {
"type": "integer",
"description": "The minimum number in the Name field in Int form",
"format": "int32",
"deprecated": true
},
"minNumber": {
"type": "number",
"description": "The minimum number in the Name field",
"format": "float"
},
"maxNumber": {
"type": "number",
"description": "The maximum number in the Name field (same as Minimum if Name isn't a range)",
"format": "float"
},
"chapters": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Chapter"
},
"nullable": true
},
"created": {
"type": "string",
"format": "date-time"
},
"lastModified": {
"type": "string",
"format": "date-time"
},
"createdUtc": {
"type": "string",
"format": "date-time"
},
"lastModifiedUtc": {
"type": "string",
"format": "date-time"
},
"coverImage": {
"type": "string",
"description": "Absolute path to the (managed) image file",
"nullable": true
},
"pages": {
"type": "integer",
"description": "Total pages of all chapters in this volume",
"format": "int32"
},
"wordCount": {
"type": "integer",
"description": "Total Word count of all chapters in this volume.",
"format": "int64"
},
"minHoursToRead": {
"type": "integer",
"format": "int32"
},
"maxHoursToRead": {
"type": "integer",
"format": "int32"
},
"avgHoursToRead": {
"type": "integer",
"format": "int32"
},
"series": {
"$ref": "#/components/schemas/Series"
},
"seriesId": {
"type": "integer",
"format": "int32"
}
},
"additionalProperties": false
},
"VolumeDto": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int32"
},
"minNumber": {
"type": "number",
"format": "float"
},
"maxNumber": {
"type": "number",
"format": "float"
},
"name": {
"type": "string",
"nullable": true
},
"number": {
"type": "integer",
"description": "This will map to MinNumber. Number was removed in v0.7.13.8/v0.7.14",
"format": "int32",
"deprecated": true
},
"pages": {
"type": "integer",
"format": "int32"
},
"pagesRead": {
"type": "integer",
"format": "int32"
},
"lastModifiedUtc": {
"type": "string",
"format": "date-time"
},
"createdUtc": {
"type": "string",
"format": "date-time"
},
"created": {
"type": "string",
"description": "When chapter was created in local server time",
"format": "date-time"
},
"lastModified": {
"type": "string",
"description": "When chapter was last modified in local server time",
"format": "date-time"
},
"seriesId": {
"type": "integer",
"format": "int32"
},
"chapters": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ChapterDto"
},
"nullable": true
},
"minHoursToRead": {
"type": "integer",
"format": "int32"
},
"maxHoursToRead": {
"type": "integer",
"format": "int32"
},
"avgHoursToRead": {
"type": "integer",
"format": "int32"
}
},
"additionalProperties": false
}
},
"securitySchemes": {
"Bearer": {
"type": "apiKey",
"description": "Please insert JWT with Bearer into field",
"name": "Authorization",
"in": "header"
}
}
},
"security": [
{
"Bearer": [ ]
}
],
"tags": [
{
"name": "Account",
"description": "All Account matters"
},
{
"name": "Cbl",
"description": "Responsible for the CBL import flow"
},
{
"name": "Collection",
"description": "APIs for Collections"
},
{
"name": "Device",
"description": "Responsible interacting and creating Devices"
},
{
"name": "Download",
"description": "All APIs related to downloading entities from the system. Requires Download Role or Admin Role."
},
{
"name": "Filter",
"description": "This is responsible for Filter caching"
},
{
"name": "Image",
"description": "Responsible for servicing up images stored in Kavita for entities"
},
{
"name": "Panels",
"description": "For the Panels app explicitly"
},
{
"name": "Rating",
"description": "Responsible for providing external ratings for Series"
},
{
"name": "Reader",
"description": "For all things regarding reading, mainly focusing on non-Book related entities"
},
{
"name": "Search",
"description": "Responsible for the Search interface from the UI"
},
{
"name": "Stream",
"description": "Responsible for anything that deals with Streams (SmartFilters, ExternalSource, DashboardStream, SideNavStream)"
},
{
"name": "Tachiyomi",
"description": "All APIs are for Tachiyomi extension and app. They have hacks for our implementation and should not be used for any\r\nother purposes."
},
{
"name": "Upload",
"description": ""
},
{
"name": "WantToRead",
"description": "Responsible for all things Want To Read"
}
]
} "nullable": true
}
},