diff --git a/komga/src/main/kotlin/org/gotson/komga/application/service/MetadataLifecycle.kt b/komga/src/main/kotlin/org/gotson/komga/application/service/MetadataLifecycle.kt index 0cd9ecf5a..e2ae333c2 100644 --- a/komga/src/main/kotlin/org/gotson/komga/application/service/MetadataLifecycle.kt +++ b/komga/src/main/kotlin/org/gotson/komga/application/service/MetadataLifecycle.kt @@ -5,6 +5,7 @@ import org.gotson.komga.domain.model.Book import org.gotson.komga.domain.persistence.BookRepository import org.gotson.komga.domain.persistence.SeriesRepository import org.gotson.komga.domain.service.MetadataApplier +import org.gotson.komga.infrastructure.metadata.BookMetadataProvider import org.gotson.komga.infrastructure.metadata.comicinfo.ComicInfoProvider import org.springframework.data.repository.findByIdOrNull import org.springframework.scheduling.annotation.Async @@ -16,6 +17,7 @@ private val logger = KotlinLogging.logger {} @Service class MetadataLifecycle( private val comicInfoProvider: ComicInfoProvider, + private val bookMetadataProviders: List, private val metadataApplier: MetadataApplier, private val bookRepository: BookRepository, private val seriesRepository: SeriesRepository @@ -28,15 +30,17 @@ class MetadataLifecycle( val loadedBook = bookRepository.findByIdOrNull(book.id) loadedBook?.let { bookToPatch -> - val patch = comicInfoProvider.getBookMetadataFromBook(bookToPatch) + bookMetadataProviders.forEach { + val patch = it.getBookMetadataFromBook(bookToPatch) - patch?.let { bPatch -> - metadataApplier.apply(bPatch, bookToPatch) - bookRepository.save(bookToPatch) + patch?.let { bPatch -> + metadataApplier.apply(bPatch, bookToPatch) + bookRepository.save(bookToPatch) - bPatch.series?.let { sPatch -> - metadataApplier.apply(sPatch, bookToPatch.series) - seriesRepository.save(bookToPatch.series) + bPatch.series?.let { sPatch -> + metadataApplier.apply(sPatch, bookToPatch.series) + seriesRepository.save(bookToPatch.series) + } } } } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/EpubExtractor.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/EpubExtractor.kt index 8278ffbdf..952f13155 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/EpubExtractor.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/EpubExtractor.kt @@ -49,6 +49,23 @@ class EpubExtractor(contentDetector: ContentDetector) : ZipExtractor(contentDete } } + private fun getPackagePath(zip: ZipFile): String = + zip.getEntry("META-INF/container.xml").let { entry -> + val container = zip.getInputStream(entry).use { Jsoup.parse(it, null, "") } + container.getElementsByTag("rootfile").first().attr("full-path") + } + + fun getPackageFile(path: Path): String? = + ZipFile(path.toFile()).use { + try { + it.getInputStream(it.getEntry(getPackagePath(it))).reader().use { it.readText() } + } catch (e: Exception) { + null + } + } + + fun Path.parentOrEmpty() = parent ?: Paths.get("") + private data class ManifestItem( val id: String, val href: String, diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/epub/EpubMetadataProvider.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/epub/EpubMetadataProvider.kt new file mode 100644 index 000000000..7e17b64c7 --- /dev/null +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/epub/EpubMetadataProvider.kt @@ -0,0 +1,84 @@ +package org.gotson.komga.infrastructure.metadata.epub + +import org.gotson.komga.domain.model.Author +import org.gotson.komga.domain.model.Book +import org.gotson.komga.domain.model.BookMetadata +import org.gotson.komga.domain.model.BookMetadataPatch +import org.gotson.komga.domain.model.SeriesMetadataPatch +import org.gotson.komga.infrastructure.mediacontainer.EpubExtractor +import org.gotson.komga.infrastructure.metadata.BookMetadataProvider +import org.jsoup.Jsoup +import org.springframework.stereotype.Service +import java.time.LocalDate +import java.time.format.DateTimeFormatter + +@Service +class EpubMetadataProvider( + private val epubExtractor: EpubExtractor +) : BookMetadataProvider { + + private val relators = mapOf( + "aut" to "writer", + "clr" to "colorist", + "cov" to "cover", + "edt" to "editor" + ) + + override fun getBookMetadataFromBook(book: Book): BookMetadataPatch? { + if (book.media.mediaType != "application/epub+zip") return null + epubExtractor.getPackageFile(book.path())?.let { packageFile -> + val opf = Jsoup.parse(packageFile.toString()) + + val title = opf.selectFirst("metadata > dc|title")?.text() + val publisher = opf.selectFirst("metadata > dc|publisher")?.text() + val description = opf.selectFirst("metadata > dc|description")?.text() + val date = opf.selectFirst("metadata > dc|date")?.text()?.let { parseDate(it) } + + + val direction = opf.getElementsByTag("spine").first().attr("page-progression-direction")?.let { + when (it) { + "rtl" -> BookMetadata.ReadingDirection.RIGHT_TO_LEFT + "ltr" -> BookMetadata.ReadingDirection.LEFT_TO_RIGHT + else -> null + } + } + + val creators = opf.select("metadata > dc|creator") + .associate { it.attr("id") to it.text() } + val creatorRefines = opf.select("metadata > meta[property=role][scheme=marc:relators]") + .associate { it.attr("refines").removePrefix("#") to it.text() } + val authors = creators.map { (id, name) -> + val role = relators[creatorRefines[id]] ?: "writer" + Author(name, role) + } + + val series = opf.selectFirst("metadata > meta[property=belongs-to-collection]")?.text() + + return BookMetadataPatch( + title = title, + summary = description, + number = null, + numberSort = null, + readingDirection = direction, + publisher = publisher, + ageRating = null, + releaseDate = date, + authors = authors, + series = SeriesMetadataPatch(series, series, null) + ) + } + return null + } + + private fun parseDate(date: String): LocalDate? = + try { + LocalDate.parse(date, DateTimeFormatter.ISO_DATE) + } catch (e: Exception) { + try { + LocalDate.parse(date, DateTimeFormatter.ISO_LOCAL_DATE) + } catch (e: Exception) { + null + } + } + +}