Create ComicInfo Metadata files on chapter download (#8033)

* generate ComicInfo files at the chapter root and inside CBZ archives on chapter download.

* Update app/src/main/java/eu/kanade/tachiyomi/source/LocalSource.kt

Co-authored-by: Andreas <andreas.everos@gmail.com>

* Improvements suggested by @ghostbear

* now creates ComicInfo files in normal chapter folders as well
use manga directly instead of converting it to SManga
truncate old files before overwriting them

Co-authored-by: Andreas <6576096+ghostbear@users.noreply.github.com>

* remove empty line after resolving merge conflict

* fixes Serializer for class 'ComicInfo' is not found error

* some changes to comments and variable names

* Revert leftover changes to archiveChapter() function

* minor cleanup

* Changed Chapter to SChapter

Co-authored-by: Andreas <andreas.everos@gmail.com>
Co-authored-by: Andreas <6576096+ghostbear@users.noreply.github.com>
This commit is contained in:
Shamicen 2022-11-11 22:16:37 +01:00 committed by GitHub
parent a8eebd824a
commit 4e628fe6de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 126 additions and 1 deletions

View File

@ -1,12 +1,14 @@
package eu.kanade.domain.manga.model
import kotlinx.serialization.Serializable
import nl.adaptivity.xmlutil.serialization.XmlElement
import nl.adaptivity.xmlutil.serialization.XmlSerialName
import nl.adaptivity.xmlutil.serialization.XmlValue
@Serializable
@XmlSerialName("ComicInfo", "", "")
data class ComicInfo(
val title: ComicInfoTitle?,
val series: ComicInfoSeries?,
val summary: ComicInfoSummary?,
val writer: ComicInfoWriter?,
@ -15,9 +17,24 @@ data class ComicInfo(
val colorist: ComicInfoColorist?,
val letterer: ComicInfoLetterer?,
val coverArtist: ComicInfoCoverArtist?,
val translator: ComicInfoTranslator?,
val genre: ComicInfoGenre?,
val tags: ComicInfoTags?,
)
val web: ComicInfoWeb?,
val publishingStatusTachiyomi: ComicInfoPublishingStatusTachiyomi?,
) {
@XmlElement(false)
@XmlSerialName("xmlns:xsd", "", "")
val xmlSchema: String = "http://www.w3.org/2001/XMLSchema"
@XmlElement(false)
@XmlSerialName("xmlns:xsi", "", "")
val xmlSchemaInstance: String = "http://www.w3.org/2001/XMLSchema-instance"
}
@Serializable
@XmlSerialName("Title", "", "")
data class ComicInfoTitle(@XmlValue(true) val value: String = "")
@Serializable
@XmlSerialName("Series", "", "")
@ -51,6 +68,10 @@ data class ComicInfoLetterer(@XmlValue(true) val value: String = "")
@XmlSerialName("CoverArtist", "", "")
data class ComicInfoCoverArtist(@XmlValue(true) val value: String = "")
@Serializable
@XmlSerialName("Translator", "", "")
data class ComicInfoTranslator(@XmlValue(true) val value: String = "")
@Serializable
@XmlSerialName("Genre", "", "")
data class ComicInfoGenre(@XmlValue(true) val value: String = "")
@ -58,3 +79,11 @@ data class ComicInfoGenre(@XmlValue(true) val value: String = "")
@Serializable
@XmlSerialName("Tags", "", "")
data class ComicInfoTags(@XmlValue(true) val value: String = "")
@Serializable
@XmlSerialName("Web", "", "")
data class ComicInfoWeb(@XmlValue(true) val value: String = "")
@Serializable
@XmlSerialName("PublishingStatusTachiyomi", "http://www.w3.org/2001/XMLSchema", "ty")
data class ComicInfoPublishingStatusTachiyomi(@XmlValue(true) val value: String = "")

View File

@ -43,6 +43,8 @@ import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
import eu.kanade.tachiyomi.util.system.isDevFlavor
import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory
import kotlinx.serialization.json.Json
import nl.adaptivity.xmlutil.XmlDeclMode
import nl.adaptivity.xmlutil.core.XmlVersion
import nl.adaptivity.xmlutil.serialization.UnknownChildHandler
import nl.adaptivity.xmlutil.serialization.XML
import uy.kohesive.injekt.api.InjektModule
@ -106,6 +108,9 @@ class AppModule(val app: Application) : InjektModule {
XML {
unknownChildHandler = UnknownChildHandler { _, _, _, _, _ -> emptyList() }
autoPolymorphic = true
xmlDeclMode = XmlDeclMode.Charset
indent = 4
xmlVersion = XmlVersion.XML10
}
}

View File

@ -6,7 +6,19 @@ import com.jakewharton.rxrelay.PublishRelay
import eu.kanade.domain.chapter.model.Chapter
import eu.kanade.domain.chapter.model.toDbChapter
import eu.kanade.domain.download.service.DownloadPreferences
import eu.kanade.domain.manga.model.ComicInfo
import eu.kanade.domain.manga.model.ComicInfoGenre
import eu.kanade.domain.manga.model.ComicInfoPenciller
import eu.kanade.domain.manga.model.ComicInfoPublishingStatusTachiyomi
import eu.kanade.domain.manga.model.ComicInfoSeries
import eu.kanade.domain.manga.model.ComicInfoSummary
import eu.kanade.domain.manga.model.ComicInfoTitle
import eu.kanade.domain.manga.model.ComicInfoTranslator
import eu.kanade.domain.manga.model.ComicInfoWeb
import eu.kanade.domain.manga.model.ComicInfoWriter
import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.track.interactor.GetTracks
import eu.kanade.domain.track.model.Track
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.cache.ChapterCache
import eu.kanade.tachiyomi.data.download.model.Download
@ -16,6 +28,8 @@ import eu.kanade.tachiyomi.data.notification.NotificationHandler
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.UnmeteredSource
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.source.online.fetchAllImageUrlsFromPageList
import eu.kanade.tachiyomi.util.lang.RetryWithDelay
@ -28,7 +42,9 @@ import eu.kanade.tachiyomi.util.storage.saveTo
import eu.kanade.tachiyomi.util.system.ImageUtil
import eu.kanade.tachiyomi.util.system.logcat
import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
import logcat.LogPriority
import nl.adaptivity.xmlutil.serialization.XML
import okhttp3.Response
import rx.Observable
import rx.android.schedulers.AndroidSchedulers
@ -36,8 +52,10 @@ import rx.schedulers.Schedulers
import rx.subscriptions.CompositeSubscription
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileOutputStream
import java.util.zip.CRC32
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
@ -63,8 +81,14 @@ class Downloader(
private val sourceManager: SourceManager = Injekt.get(),
private val chapterCache: ChapterCache = Injekt.get(),
private val downloadPreferences: DownloadPreferences = Injekt.get(),
private val getTracks: GetTracks = Injekt.get(),
) {
/**
* xml format used for ComicInfo files
*/
private val xml: XML by injectLazy()
/**
* Store for persisting downloads across restarts.
*/
@ -513,6 +537,8 @@ class Downloader(
// Ensure that the chapter folder has all the images.
val downloadedImages = tmpDir.listFiles().orEmpty().filterNot { it.name!!.endsWith(".tmp") || (it.name!!.contains("__") && !it.name!!.contains("__001.jpg")) }
createComicInfoFile(tmpDir, download.manga, download.chapter)
download.status = if (downloadedImages.size == download.pages!!.size) {
// Only rename the directory if it's downloaded.
if (downloadPreferences.saveChaptersAsCBZ().get()) {
@ -524,6 +550,8 @@ class Downloader(
DiskUtil.createNoMediaFile(tmpDir, context)
createComicInfoFile(mangaDir, download.manga, download.chapter)
Download.State.DOWNLOADED
} else {
Download.State.ERROR
@ -564,6 +592,59 @@ class Downloader(
tmpDir.delete()
}
/**
* Creates a ComicInfo.xml file inside the given directory.
*
* @param dir the directory in which the ComicInfo file will be generated.
* @param manga the manga of the chapter to download.
* @param chapter the chapter to download
*/
private fun createComicInfoFile(
dir: UniFile,
manga: Manga,
chapter: SChapter,
) {
File("${dir.filePath}/ComicInfo.xml").outputStream().also {
// Force overwrite old file
(it as? FileOutputStream)?.channel?.truncate(0)
}.use { it.write(getComicInfo(manga, chapter)) }
}
/**
* returns a ByteArray containing the Manga Metadata of the chapter to download in ComicInfo.xml format
*
* @param manga the manga of the chapter to download.
* @param chapter the name of the chapter to download
*/
private fun getComicInfo(manga: Manga, chapter: SChapter): ByteArray {
val track: Track? = runBlocking { getTracks.await(manga.id).firstOrNull() }
val comicInfo = ComicInfo(
title = ComicInfoTitle(chapter.name),
series = ComicInfoSeries(manga.title),
summary = manga.description?.let { ComicInfoSummary(it) },
writer = manga.author?.let { ComicInfoWriter(it) },
penciller = manga.artist?.let { ComicInfoPenciller(it) },
translator = chapter.scanlator?.let { ComicInfoTranslator(it) },
genre = manga.genre?.let { ComicInfoGenre(it.joinToString()) },
web = track?.remoteUrl?.let { ComicInfoWeb(it) },
publishingStatusTachiyomi = when (manga.status) {
SManga.ONGOING.toLong() -> ComicInfoPublishingStatusTachiyomi("Ongoing")
SManga.COMPLETED.toLong() -> ComicInfoPublishingStatusTachiyomi("Completed")
SManga.LICENSED.toLong() -> ComicInfoPublishingStatusTachiyomi("Licensed")
SManga.PUBLISHING_FINISHED.toLong() -> ComicInfoPublishingStatusTachiyomi("Publishing finished")
SManga.CANCELLED.toLong() -> ComicInfoPublishingStatusTachiyomi("Cancelled")
SManga.ON_HIATUS.toLong() -> ComicInfoPublishingStatusTachiyomi("On hiatus")
else -> ComicInfoPublishingStatusTachiyomi("Unknown")
},
inker = null,
colorist = null,
letterer = null,
coverArtist = null,
tags = null,
)
return xml.encodeToString(ComicInfo.serializer(), comicInfo).toByteArray()
}
/**
* Completes a download. This method is called in the main thread.
*/

View File

@ -269,6 +269,16 @@ class LocalSource(
.joinToString(", ") { it.trim() }
.takeIf { it.isNotEmpty() }
?.let { manga.artist = it }
manga.status = when (comicInfo.publishingStatusTachiyomi?.value) {
"Ongoing" -> SManga.ONGOING
"Completed" -> SManga.COMPLETED
"Licensed" -> SManga.LICENSED
"Publishing finished" -> SManga.PUBLISHING_FINISHED
"Cancelled" -> SManga.CANCELLED
"On hiatus" -> SManga.ON_HIATUS
else -> SManga.UNKNOWN
}
}
@Serializable