A boatload of Bugs (#3704)

Co-authored-by: Amelia <77553571+Fesaa@users.noreply.github.com>
This commit is contained in:
Joe Milazzo 2025-04-05 15:52:01 -05:00 committed by GitHub
parent ea9b7ad0d1
commit 37734554ba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
102 changed files with 2051 additions and 1115 deletions

View file

@ -85,11 +85,32 @@ public class BookService : IBookService
},
Epub2NcxReaderOptions = new Epub2NcxReaderOptions
{
IgnoreMissingContentForNavigationPoints = true
IgnoreMissingContentForNavigationPoints = false
},
SpineReaderOptions = new SpineReaderOptions
{
IgnoreMissingManifestItems = true
IgnoreMissingManifestItems = false
},
BookCoverReaderOptions = new BookCoverReaderOptions
{
Epub2MetadataIgnoreMissingManifestItem = false
}
};
public static readonly EpubReaderOptions LenientBookReaderOptions = new()
{
PackageReaderOptions = new PackageReaderOptions
{
IgnoreMissingToc = true,
SkipInvalidManifestItems = true,
},
Epub2NcxReaderOptions = new Epub2NcxReaderOptions
{
IgnoreMissingContentForNavigationPoints = false
},
SpineReaderOptions = new SpineReaderOptions
{
IgnoreMissingManifestItems = false
},
BookCoverReaderOptions = new BookCoverReaderOptions
{
@ -455,9 +476,12 @@ public class BookService : IBookService
private ComicInfo? GetEpubComicInfo(string filePath)
{
EpubBookRef? epubBook = null;
try
{
using var epubBook = EpubReader.OpenBook(filePath, BookReaderOptions);
epubBook = OpenEpubWithFallback(filePath, epubBook);
var publicationDate =
epubBook.Schema.Package.Metadata.Dates.Find(pDate => pDate.Event == "publication")?.Date;
@ -465,6 +489,7 @@ public class BookService : IBookService
{
publicationDate = epubBook.Schema.Package.Metadata.Dates.FirstOrDefault()?.Date;
}
var (year, month, day) = GetPublicationDate(publicationDate);
var summary = epubBook.Schema.Package.Metadata.Descriptions.FirstOrDefault();
@ -476,7 +501,8 @@ public class BookService : IBookService
Day = day,
Year = year,
Title = epubBook.Title,
Genre = string.Join(",", epubBook.Schema.Package.Metadata.Subjects.Select(s => s.Subject.ToLower().Trim())),
Genre = string.Join(",",
epubBook.Schema.Package.Metadata.Subjects.Select(s => s.Subject.ToLower().Trim())),
LanguageISO = ValidateLanguage(epubBook.Schema.Package.Metadata.Languages
.Select(l => l.Language)
.FirstOrDefault())
@ -487,7 +513,8 @@ public class BookService : IBookService
foreach (var identifier in epubBook.Schema.Package.Metadata.Identifiers)
{
if (string.IsNullOrEmpty(identifier.Identifier)) continue;
if (!string.IsNullOrEmpty(identifier.Scheme) && identifier.Scheme.Equals("ISBN", StringComparison.InvariantCultureIgnoreCase))
if (!string.IsNullOrEmpty(identifier.Scheme) &&
identifier.Scheme.Equals("ISBN", StringComparison.InvariantCultureIgnoreCase))
{
var isbn = identifier.Identifier.Replace("urn:isbn:", string.Empty).Replace("isbn:", string.Empty);
if (!ArticleNumberHelper.IsValidIsbn10(isbn) && !ArticleNumberHelper.IsValidIsbn13(isbn))
@ -495,11 +522,13 @@ public class BookService : IBookService
_logger.LogDebug("[BookService] {File} has invalid ISBN number", filePath);
continue;
}
info.Isbn = isbn;
}
if ((!string.IsNullOrEmpty(identifier.Scheme) && identifier.Scheme.Equals("URL", StringComparison.InvariantCultureIgnoreCase)) ||
identifier.Identifier.StartsWith("url:"))
if ((!string.IsNullOrEmpty(identifier.Scheme) &&
identifier.Scheme.Equals("URL", StringComparison.InvariantCultureIgnoreCase)) ||
identifier.Identifier.StartsWith("url:"))
{
var url = identifier.Identifier.Replace("url:", string.Empty);
weblinks.Add(url.Trim());
@ -529,6 +558,7 @@ public class BookService : IBookService
{
info.SeriesSort = metadataItem.Content;
}
break;
case "calibre:series_index":
info.Volume = metadataItem.Content;
@ -548,6 +578,7 @@ public class BookService : IBookService
{
info.SeriesSort = metadataItem.Content;
}
break;
case "collection-type":
// These look to be genres from https://manual.calibre-ebook.com/sub_groups.html or can be "series"
@ -578,7 +609,8 @@ public class BookService : IBookService
}
// If this is a single book and not a collection, set publication status to Completed
if (string.IsNullOrEmpty(info.Volume) && Parser.ParseVolume(filePath, LibraryType.Manga).Equals(Parser.LooseLeafVolume))
if (string.IsNullOrEmpty(info.Volume) &&
Parser.ParseVolume(filePath, LibraryType.Manga).Equals(Parser.LooseLeafVolume))
{
info.Count = 1;
}
@ -590,7 +622,8 @@ public class BookService : IBookService
var hasVolumeInSeries = !Parser.ParseVolume(info.Title, LibraryType.Manga)
.Equals(Parser.LooseLeafVolume);
if (string.IsNullOrEmpty(info.Volume) && hasVolumeInSeries && (!info.Series.Equals(info.Title) || string.IsNullOrEmpty(info.Series)))
if (string.IsNullOrEmpty(info.Volume) && hasVolumeInSeries &&
(!info.Series.Equals(info.Title) || string.IsNullOrEmpty(info.Series)))
{
// This is likely a light novel for which we can set series from parsed title
info.Series = Parser.ParseSeries(info.Title, LibraryType.Manga);
@ -601,14 +634,40 @@ public class BookService : IBookService
}
catch (Exception ex)
{
_logger.LogWarning(ex, "[GetComicInfo] There was an exception parsing metadata");
_logger.LogWarning(ex, "[GetComicInfo] There was an exception parsing metadata: {FilePath}", filePath);
_mediaErrorService.ReportMediaIssue(filePath, MediaErrorProducer.BookService,
"There was an exception parsing metadata", ex);
}
finally
{
epubBook?.Dispose();
}
return null;
}
private EpubBookRef? OpenEpubWithFallback(string filePath, EpubBookRef? epubBook)
{
try
{
epubBook = EpubReader.OpenBook(filePath, BookReaderOptions);
}
catch (Exception ex)
{
_logger.LogWarning(ex,
"[GetComicInfo] There was an exception parsing metadata, falling back to a more lenient parsing method: {FilePath}",
filePath);
_mediaErrorService.ReportMediaIssue(filePath, MediaErrorProducer.BookService,
"There was an exception parsing metadata", ex);
}
finally
{
epubBook ??= EpubReader.OpenBook(filePath, LenientBookReaderOptions);
}
return epubBook;
}
public ComicInfo? GetComicInfo(string filePath)
{
if (!IsValidFile(filePath)) return null;
@ -765,7 +824,7 @@ public class BookService : IBookService
return docReader.GetPageCount();
}
using var epubBook = EpubReader.OpenBook(filePath, BookReaderOptions);
using var epubBook = EpubReader.OpenBook(filePath, LenientBookReaderOptions);
return epubBook.GetReadingOrder().Count;
}
catch (Exception ex)
@ -823,7 +882,7 @@ public class BookService : IBookService
try
{
using var epubBook = EpubReader.OpenBook(filePath, BookReaderOptions);
using var epubBook = EpubReader.OpenBook(filePath, LenientBookReaderOptions);
// <meta content="The Dark Tower" name="calibre:series"/>
// <meta content="Wolves of the Calla" name="calibre:title_sort"/>
@ -1027,7 +1086,7 @@ public class BookService : IBookService
/// <returns></returns>
public async Task<ICollection<BookChapterItem>> GenerateTableOfContents(Chapter chapter)
{
using var book = await EpubReader.OpenBookAsync(chapter.Files.ElementAt(0).FilePath, BookReaderOptions);
using var book = await EpubReader.OpenBookAsync(chapter.Files.ElementAt(0).FilePath, LenientBookReaderOptions);
var mappings = await CreateKeyToPageMappingAsync(book);
var navItems = await book.GetNavigationAsync();
@ -1155,7 +1214,7 @@ public class BookService : IBookService
/// <exception cref="KavitaException">All exceptions throw this</exception>
public async Task<string> GetBookPage(int page, int chapterId, string cachedEpubPath, string baseUrl)
{
using var book = await EpubReader.OpenBookAsync(cachedEpubPath, BookReaderOptions);
using var book = await EpubReader.OpenBookAsync(cachedEpubPath, LenientBookReaderOptions);
var mappings = await CreateKeyToPageMappingAsync(book);
var apiBase = baseUrl + "book/" + chapterId + "/" + BookApiUrl;
@ -1257,7 +1316,7 @@ public class BookService : IBookService
return GetPdfCoverImage(fileFilePath, fileName, outputDirectory, encodeFormat, size);
}
using var epubBook = EpubReader.OpenBook(fileFilePath, BookReaderOptions);
using var epubBook = EpubReader.OpenBook(fileFilePath, LenientBookReaderOptions);
try
{