diff --git a/API.Tests/Services/BookServiceTests.cs b/API.Tests/Services/BookServiceTests.cs index af5805cc5..a80c1ca01 100644 --- a/API.Tests/Services/BookServiceTests.cs +++ b/API.Tests/Services/BookServiceTests.cs @@ -1,6 +1,8 @@ using System.IO; using System.IO.Abstractions; +using API.Entities.Enums; using API.Services; +using API.Services.Tasks.Scanner.Parser; using Microsoft.Extensions.Logging; using NSubstitute; using Xunit; @@ -122,4 +124,22 @@ public class BookServiceTests var comicInfo = _bookService.GetComicInfo(document); Assert.Null(comicInfo); } + + [Fact] + public void SeriesFallBackToMetadataTitle() + { + var ds = new DirectoryService(Substitute.For>(), new FileSystem()); + var pdfParser = new PdfParser(ds); + + var testDirectory = Path.Join(Directory.GetCurrentDirectory(), "../../../Services/Test Data/BookService"); + var filePath = Path.Join(testDirectory, "Bizet-Variations_Chromatiques_de_concert_Theme_A4.pdf"); + + var comicInfo = _bookService.GetComicInfo(filePath); + Assert.NotNull(comicInfo); + + var parserInfo = pdfParser.Parse(filePath, testDirectory, ds.GetParentDirectoryName(testDirectory), LibraryType.Book, comicInfo); + Assert.NotNull(parserInfo); + Assert.Equal(parserInfo.Title, comicInfo.Title); + Assert.Equal(parserInfo.Series, comicInfo.Title); + } } diff --git a/API.Tests/Services/Test Data/BookService/Bizet-Variations_Chromatiques_de_concert_Theme_A4.pdf b/API.Tests/Services/Test Data/BookService/Bizet-Variations_Chromatiques_de_concert_Theme_A4.pdf new file mode 100644 index 000000000..9fe4811a7 Binary files /dev/null and b/API.Tests/Services/Test Data/BookService/Bizet-Variations_Chromatiques_de_concert_Theme_A4.pdf differ diff --git a/API/Entities/Chapter.cs b/API/Entities/Chapter.cs index fc709961b..3f96472ca 100644 --- a/API/Entities/Chapter.cs +++ b/API/Entities/Chapter.cs @@ -178,8 +178,7 @@ public class Chapter : IEntityDate, IHasReadTimeEstimate, IHasCoverImage MinNumber = Parser.DefaultChapterNumber; MaxNumber = Parser.DefaultChapterNumber; } - // NOTE: This doesn't work well for all because Pdf usually should use into.Title or even filename - Title = (IsSpecial && info.Format == MangaFormat.Epub) + Title = (IsSpecial && info.Format is MangaFormat.Epub or MangaFormat.Pdf) ? info.Title : Parser.RemoveExtensionIfSupported(Range); diff --git a/API/Helpers/Builders/ChapterBuilder.cs b/API/Helpers/Builders/ChapterBuilder.cs index 348696820..e7c458eeb 100644 --- a/API/Helpers/Builders/ChapterBuilder.cs +++ b/API/Helpers/Builders/ChapterBuilder.cs @@ -39,9 +39,9 @@ public class ChapterBuilder : IEntityBuilder return builder.WithNumber(Parser.RemoveExtensionIfSupported(info.Chapters)!) .WithRange(specialTreatment ? info.Filename : info.Chapters) - .WithTitle((specialTreatment && info.Format == MangaFormat.Epub) + .WithTitle(specialTreatment && info.Format is MangaFormat.Epub or MangaFormat.Pdf ? info.Title - : specialTitle) + : specialTitle ?? string.Empty) .WithIsSpecial(specialTreatment); } diff --git a/API/Services/Tasks/Scanner/Parser/PdfParser.cs b/API/Services/Tasks/Scanner/Parser/PdfParser.cs index 3a5debcbd..bc12e2c77 100644 --- a/API/Services/Tasks/Scanner/Parser/PdfParser.cs +++ b/API/Services/Tasks/Scanner/Parser/PdfParser.cs @@ -71,6 +71,11 @@ public class PdfParser(IDirectoryService directoryService) : DefaultParser(direc // Patch in other information from ComicInfo UpdateFromComicInfo(ret); + if (comicInfo != null && !string.IsNullOrEmpty(comicInfo.Title)) + { + ret.Title = comicInfo.Title.Trim(); + } + if (ret.Chapters == Parser.DefaultChapter && ret.Volumes == Parser.LooseLeafVolume && type == LibraryType.Book) { ret.IsSpecial = true; @@ -79,6 +84,19 @@ public class PdfParser(IDirectoryService directoryService) : DefaultParser(direc ParseFromFallbackFolders(filePath, rootPath, type, ref ret); } + if (type == LibraryType.Book && comicInfo != null) + { + // For books, fall back to the Title for Series. + if (!string.IsNullOrEmpty(comicInfo.Series)) + { + ret.Series = comicInfo.Series.Trim(); + } + else if (!string.IsNullOrEmpty(comicInfo.Title)) + { + ret.Series = comicInfo.Title.Trim(); + } + } + if (string.IsNullOrEmpty(ret.Series)) { ret.Series = Parser.CleanTitle(fileName, type is LibraryType.Comic); diff --git a/UI/Web/src/app/_pipes/time-ago.pipe.ts b/UI/Web/src/app/_pipes/time-ago.pipe.ts index 5780b18ab..9940d4bb7 100644 --- a/UI/Web/src/app/_pipes/time-ago.pipe.ts +++ b/UI/Web/src/app/_pipes/time-ago.pipe.ts @@ -40,7 +40,7 @@ export class TimeAgoPipe implements PipeTransform, OnDestroy { private translocoService: TranslocoService) {} transform(value: string | Date | null) { - if (value === '' || value === null || value === undefined || (value instanceof String && value.split('T')[0] === '0001-01-01')) { + if (value === '' || value === null || value === undefined || (typeof value === 'string' && value.split('T')[0] === '0001-01-01')) { return this.translocoService.translate('time-ago-pipe.never'); }