feat(komga): support for RAR5 via libarchive

This commit is contained in:
Gauthier Roebroeck 2024-01-04 17:59:15 +08:00
parent 7dd05a5037
commit 4c1301f45e
5 changed files with 76 additions and 3 deletions

View file

@ -89,6 +89,7 @@ dependencies {
implementation("org.apache.tika:tika-core:2.9.1")
implementation("org.apache.commons:commons-compress:1.24.0")
implementation("com.github.junrar:junrar:7.5.5")
implementation("com.github.gotson.nightcompress:nightcompress:0.2.0")
implementation("org.apache.pdfbox:pdfbox:2.0.28")
implementation("net.grey-panther:natural-comparator:1.1")
implementation("org.jsoup:jsoup:1.16.2")

View file

@ -4,6 +4,7 @@ enum class MediaType(val type: String, val profile: MediaProfile, val fileExtens
ZIP("application/zip", MediaProfile.DIVINA, "cbz", "application/vnd.comicbook+zip"),
RAR_GENERIC("application/x-rar-compressed", MediaProfile.DIVINA, "cbr", "application/vnd.comicbook-rar"),
RAR_4("application/x-rar-compressed; version=4", MediaProfile.DIVINA, "cbr", "application/vnd.comicbook-rar"),
RAR_5("application/x-rar-compressed; version=5", MediaProfile.DIVINA, "cbr", "application/vnd.comicbook-rar"),
EPUB("application/epub+zip", MediaProfile.EPUB, "epub"),
PDF("application/pdf", MediaProfile.PDF, "pdf"),
;

View file

@ -91,7 +91,8 @@ class BookAnalyzer(
private fun analyzeDivina(book: Book, mediaType: MediaType, analyzeDimensions: Boolean): Media {
val entries = try {
divinaExtractors.getValue(mediaType.type).getEntries(book.path, analyzeDimensions)
divinaExtractors[mediaType.type]?.getEntries(book.path, analyzeDimensions)
?: return Media(status = Media.Status.UNSUPPORTED)
} catch (ex: MediaUnsupportedException) {
return Media(status = Media.Status.UNSUPPORTED, comment = ex.code)
} catch (ex: Exception) {

View file

@ -50,10 +50,10 @@ class BookConverter(
private val historicalEventRepository: HistoricalEventRepository,
) {
private val convertibleTypes = listOf(MediaType.RAR_4.type)
private val convertibleTypes = listOf(MediaType.RAR_4.type, MediaType.RAR_5.type)
private val mediaTypeToExtension =
listOf(MediaType.RAR_4, MediaType.ZIP, MediaType.PDF, MediaType.EPUB)
listOf(MediaType.RAR_4, MediaType.RAR_5, MediaType.ZIP, MediaType.PDF, MediaType.EPUB)
.associate { it.type to it.fileExtension }
private val failedConversions = mutableListOf<String>()

View file

@ -0,0 +1,70 @@
package org.gotson.komga.infrastructure.mediacontainer.divina
import com.github.gotson.nightcompress.Archive
import mu.KotlinLogging
import net.greypanther.natsort.CaseInsensitiveSimpleNaturalComparator
import org.gotson.komga.domain.model.MediaContainerEntry
import org.gotson.komga.domain.model.MediaType
import org.gotson.komga.infrastructure.image.ImageAnalyzer
import org.gotson.komga.infrastructure.mediacontainer.ContentDetector
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory
import org.springframework.beans.factory.support.BeanDefinitionBuilder
import org.springframework.beans.factory.support.BeanDefinitionRegistry
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor
import org.springframework.context.annotation.Configuration
import java.nio.file.Path
private val logger = KotlinLogging.logger {}
@Configuration
class Rar5Configuration : BeanDefinitionRegistryPostProcessor {
override fun postProcessBeanFactory(beanFactory: ConfigurableListableBeanFactory) {}
override fun postProcessBeanDefinitionRegistry(registry: BeanDefinitionRegistry) {
try {
if (Archive.isAvailable()) {
val builder = BeanDefinitionBuilder.genericBeanDefinition(Rar5Extractor::class.java).setLazyInit(true)
registry.registerBeanDefinition("rar5Extractor", builder.beanDefinition)
logger.info { "Rar5 extractor is enabled" }
}
} catch (e: Throwable) {
logger.warn(e) { "Rar5 extractor count not be loaded" }
}
}
}
class Rar5Extractor(
private val contentDetector: ContentDetector,
private val imageAnalyzer: ImageAnalyzer,
) : DivinaExtractor {
private val natSortComparator: Comparator<String> = CaseInsensitiveSimpleNaturalComparator.getInstance()
override fun mediaTypes(): List<String> = listOf(MediaType.RAR_5.type)
override fun getEntries(path: Path, analyzeDimensions: Boolean): List<MediaContainerEntry> =
Archive(path).use { rar ->
generateSequence { rar.nextEntry }
.map { entry ->
try {
val buffer = rar.inputStream.use { it.readBytes() }
val mediaType = buffer.inputStream().use { contentDetector.detectMediaType(it) }
val dimension = if (analyzeDimensions && contentDetector.isImage(mediaType))
buffer.inputStream().use { imageAnalyzer.getDimension(it) }
else
null
val fileSize = entry.size
MediaContainerEntry(name = entry.name, mediaType = mediaType, dimension = dimension, fileSize = fileSize)
} catch (e: Exception) {
logger.warn(e) { "Could not analyze entry: ${entry.name}" }
MediaContainerEntry(name = entry.name, comment = e.message)
}
}
.sortedWith(compareBy(natSortComparator) { it.name })
.toList()
}
override fun getEntryStream(path: Path, entryName: String): ByteArray =
Archive.getInputStream(path, entryName).use { it?.readBytes() ?: ByteArray(0) }
}