PDF Metadata Support (#3552)

Co-authored-by: Matthias Neeracher <microtherion@gmail.com>
This commit is contained in:
Joe Milazzo 2025-02-16 15:10:15 -06:00 committed by GitHub
parent 56108eb373
commit f76de42b28
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 1949 additions and 57 deletions

View file

@ -6,12 +6,14 @@ using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml;
using API.Data.Metadata;
using API.DTOs.Reader;
using API.Entities;
using API.Entities.Enums;
using API.Extensions;
using API.Services.Tasks.Scanner.Parser;
using API.Helpers;
using Docnet.Core;
using Docnet.Core.Converters;
using Docnet.Core.Models;
@ -69,6 +71,8 @@ public class BookService : IBookService
private static readonly RecyclableMemoryStreamManager StreamManager = new ();
private const string CssScopeClass = ".book-content";
private const string BookApiUrl = "book-resources?file=";
private readonly PdfComicInfoExtractor _pdfComicInfoExtractor;
public static readonly EpubReaderOptions BookReaderOptions = new()
{
PackageReaderOptions = new PackageReaderOptions
@ -84,6 +88,7 @@ public class BookService : IBookService
_directoryService = directoryService;
_imageService = imageService;
_mediaErrorService = mediaErrorService;
_pdfComicInfoExtractor = new PdfComicInfoExtractor(_logger, _mediaErrorService);
}
private static bool HasClickableHrefPart(HtmlNode anchor)
@ -425,10 +430,8 @@ public class BookService : IBookService
}
}
public ComicInfo? GetComicInfo(string filePath)
private ComicInfo? GetEpubComicInfo(string filePath)
{
if (!IsValidFile(filePath) || Parser.IsPdf(filePath)) return null;
try
{
using var epubBook = EpubReader.OpenBook(filePath, BookReaderOptions);
@ -442,7 +445,7 @@ public class BookService : IBookService
var (year, month, day) = GetPublicationDate(publicationDate);
var summary = epubBook.Schema.Package.Metadata.Descriptions.FirstOrDefault();
var info = new ComicInfo
var info = new ComicInfo
{
Summary = string.IsNullOrEmpty(summary?.Description) ? string.Empty : summary.Description,
Publisher = string.Join(",", epubBook.Schema.Package.Metadata.Publishers.Select(p => p.Publisher)),
@ -583,6 +586,20 @@ public class BookService : IBookService
return null;
}
public ComicInfo? GetComicInfo(string filePath)
{
if (!IsValidFile(filePath)) return null;
if (Parser.IsPdf(filePath))
{
return _pdfComicInfoExtractor.GetComicInfo(filePath);
}
else
{
return GetEpubComicInfo(filePath);
}
}
private static void ExtractSortTitle(EpubMetadataMeta metadataItem, EpubBookRef epubBook, ComicInfo info)
{
var titleId = metadataItem.Refines?.Replace("#", string.Empty);
@ -685,7 +702,7 @@ public class BookService : IBookService
return (year, month, day);
}
private static string ValidateLanguage(string? language)
public static string ValidateLanguage(string? language)
{
if (string.IsNullOrEmpty(language)) return string.Empty;

View file

@ -566,7 +566,6 @@ public class ExternalMetadataService : IExternalMetadataService
return false;
}
var relatedSeriesDict = new Dictionary<int, Series>();
foreach (var relation in externalMetadataRelations)
{
var names = new [] {relation.SeriesName.PreferredTitle, relation.SeriesName.RomajiTitle, relation.SeriesName.EnglishTitle, relation.SeriesName.NativeTitle};
@ -586,19 +585,6 @@ public class ExternalMetadataService : IExternalMetadataService
if (relationshipExists) continue;
relatedSeriesDict[relatedSeries.Id] = relatedSeries;
}
// Process relationships
foreach (var relation in externalMetadataRelations)
{
var relatedSeries = relatedSeriesDict.GetValueOrDefault(
relatedSeriesDict.Keys.FirstOrDefault(k =>
relatedSeriesDict[k].Name == relation.SeriesName.PreferredTitle ||
relatedSeriesDict[k].Name == relation.SeriesName.NativeTitle));
if (relatedSeries == null) continue;
// Add new relationship
var newRelation = new SeriesRelation
{
@ -969,7 +955,7 @@ public class ExternalMetadataService : IExternalMetadataService
return false;
}
if (!string.IsNullOrEmpty(externalMetadata.CoverUrl) && !settings.HasOverride(MetadataSettingField.Covers))
if (string.IsNullOrEmpty(externalMetadata.CoverUrl))
{
return false;
}

View file

@ -52,7 +52,7 @@ public class ReadingItemService : IReadingItemService
/// <returns></returns>
private ComicInfo? GetComicInfo(string filePath)
{
if (Parser.IsEpub(filePath))
if (Parser.IsEpub(filePath) || Parser.IsPdf(filePath))
{
return _bookService.GetComicInfo(filePath);
}

View file

@ -68,6 +68,9 @@ public class PdfParser(IDirectoryService directoryService) : DefaultParser(direc
ParseFromFallbackFolders(filePath, tempRootPath, type, ref ret);
}
// Patch in other information from ComicInfo
UpdateFromComicInfo(ret);
if (ret.Chapters == Parser.DefaultChapter && ret.Volumes == Parser.LooseLeafVolume && type == LibraryType.Book)
{
ret.IsSpecial = true;

View file

@ -285,7 +285,7 @@ public class ProcessSeries : IProcessSeries
var firstChapter = SeriesService.GetFirstChapterForMetadata(series);
var firstFile = firstChapter?.Files.FirstOrDefault();
if (firstFile == null || Parser.Parser.IsPdf(firstFile.FilePath)) return;
if (firstFile == null) return;
var chapters = series.Volumes
.SelectMany(volume => volume.Chapters)