diff --git a/komga-webui/src/functions/book-format.ts b/komga-webui/src/functions/book-format.ts index 062f79c04..40340c867 100644 --- a/komga-webui/src/functions/book-format.ts +++ b/komga-webui/src/functions/book-format.ts @@ -6,6 +6,8 @@ export function getBookFormatFromMediaType (mediaType: string): BookFormat { return { type: 'CBZ', color: '#4CAF50' } case 'application/pdf': return { type: 'PDF', color: '#FF5722' } + case 'application/epub+zip': + return { type: 'EPUB', color: '#ff5ab1' } default: return { type: '?', color: '#000000' } } diff --git a/komga/build.gradle.kts b/komga/build.gradle.kts index 3905b7d0a..a8dbd40c6 100644 --- a/komga/build.gradle.kts +++ b/komga/build.gradle.kts @@ -69,6 +69,7 @@ dependencies { implementation("com.github.junrar:junrar:4.0.0") implementation("org.apache.pdfbox:pdfbox:2.0.19") implementation("net.grey-panther:natural-comparator:1.1") + implementation("org.jsoup:jsoup:1.13.1") implementation("net.coobird:thumbnailator:0.4.11") implementation("com.twelvemonkeys.imageio:imageio-jpeg:3.5") diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/FileSystemScanner.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/FileSystemScanner.kt index d49a3b30c..864cb5081 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/service/FileSystemScanner.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/FileSystemScanner.kt @@ -24,7 +24,7 @@ class FileSystemScanner( private val komgaProperties: KomgaProperties ) { - val supportedExtensions = listOf("cbz", "zip", "cbr", "rar", "pdf") + val supportedExtensions = listOf("cbz", "zip", "cbr", "rar", "pdf", "epub") fun scanRootFolder(root: Path): List { logger.info { "Scanning folder: $root" } 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 new file mode 100644 index 000000000..8278ffbdf --- /dev/null +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/EpubExtractor.kt @@ -0,0 +1,57 @@ +package org.gotson.komga.infrastructure.mediacontainer + +import mu.KotlinLogging +import org.apache.commons.compress.archivers.zip.ZipFile +import org.gotson.komga.domain.model.MediaContainerEntry +import org.jsoup.Jsoup +import org.springframework.stereotype.Service +import java.nio.file.Path +import java.nio.file.Paths + +private val logger = KotlinLogging.logger {} + +@Service +class EpubExtractor(contentDetector: ContentDetector) : ZipExtractor(contentDetector) { + + override fun mediaTypes(): List = listOf("application/epub+zip") + + override fun getEntries(path: Path): List { + ZipFile(path.toFile()).use { zip -> + try { + val packagePath = 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") + } + + val opf = zip.getInputStream(zip.getEntry(packagePath)).use { Jsoup.parse(it, null, "") } + + val manifest = opf.select("manifest > item") + .associate { it.attr("id") to ManifestItem(it.attr("id"), it.attr("href"), it.attr("media-type")) } + + val pages = opf.select("spine > itemref").map { it.attr("idref") } + .mapNotNull { manifest[it] } + .map { it.href } + + val images = pages.flatMap { pagePath -> + val doc = zip.getInputStream(zip.getEntry(pagePath)).use { Jsoup.parse(it, null, "") } + doc.getElementsByTag("img") + .map { it.attr("src") } + .map { Paths.get(pagePath).parent?.resolve(it).toString() } + } + + return images.map { image -> + MediaContainerEntry(image, manifest.values.first { it.href == image }.mediaType) + } + } catch (e: Exception) { + logger.error(e) { "File is not a proper Epub, treating it as a zip file" } + return super.getEntries(path) + } + } + } + + private data class ManifestItem( + val id: String, + val href: String, + val mediaType: String + ) +}