Automatically convert details.json to ComicInfo.xml for local series
Originally contributed as #9603 I ended up coming back to this since it seems like a reasonable way to migrate users in the short-medium term. We'll remove this in a later release. Co-authored-by: Shamicen <Shamicen@users.noreply.github.com>
This commit is contained in:
parent
b7d282235d
commit
79b37df647
3 changed files with 50 additions and 19 deletions
|
@ -8,6 +8,27 @@ import nl.adaptivity.xmlutil.serialization.XmlValue
|
|||
|
||||
const val COMIC_INFO_FILE = "ComicInfo.xml"
|
||||
|
||||
fun SManga.getComicInfo() = ComicInfo(
|
||||
series = ComicInfo.Series(title),
|
||||
summary = description?.let { ComicInfo.Summary(it) },
|
||||
writer = author?.let { ComicInfo.Writer(it) },
|
||||
penciller = artist?.let { ComicInfo.Penciller(it) },
|
||||
genre = genre?.let { ComicInfo.Genre(it) },
|
||||
publishingStatus = ComicInfo.PublishingStatusTachiyomi(
|
||||
ComicInfoPublishingStatus.toComicInfoValue(status.toLong()),
|
||||
),
|
||||
title = null,
|
||||
number = null,
|
||||
web = null,
|
||||
translator = null,
|
||||
inker = null,
|
||||
colorist = null,
|
||||
letterer = null,
|
||||
coverArtist = null,
|
||||
tags = null,
|
||||
categories = null,
|
||||
)
|
||||
|
||||
fun SManga.copyFromComicInfo(comicInfo: ComicInfo) {
|
||||
comicInfo.series?.let { title = it.value }
|
||||
comicInfo.writer?.let { author = it.value }
|
||||
|
@ -39,6 +60,8 @@ fun SManga.copyFromComicInfo(comicInfo: ComicInfo) {
|
|||
status = ComicInfoPublishingStatus.toSMangaValue(comicInfo.publishingStatus?.value)
|
||||
}
|
||||
|
||||
// https://anansi-project.github.io/docs/comicinfo/schemas/v2.0
|
||||
@Suppress("UNUSED")
|
||||
@Serializable
|
||||
@XmlSerialName("ComicInfo", "", "")
|
||||
data class ComicInfo(
|
||||
|
@ -59,12 +82,10 @@ data class ComicInfo(
|
|||
val publishingStatus: PublishingStatusTachiyomi?,
|
||||
val categories: CategoriesTachiyomi?,
|
||||
) {
|
||||
@Suppress("UNUSED")
|
||||
@XmlElement(false)
|
||||
@XmlSerialName("xmlns:xsd", "", "")
|
||||
val xmlSchema: String = "http://www.w3.org/2001/XMLSchema"
|
||||
|
||||
@Suppress("UNUSED")
|
||||
@XmlElement(false)
|
||||
@XmlSerialName("xmlns:xsi", "", "")
|
||||
val xmlSchemaInstance: String = "http://www.w3.org/2001/XMLSchema-instance"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package tachiyomi.source.local
|
||||
|
||||
import android.content.Context
|
||||
import com.hippo.unifile.UniFile
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.UnmeteredSource
|
||||
|
@ -10,7 +11,6 @@ import eu.kanade.tachiyomi.source.model.SChapter
|
|||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
|
||||
import eu.kanade.tachiyomi.util.storage.EpubFile
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.decodeFromStream
|
||||
import logcat.LogPriority
|
||||
|
@ -19,6 +19,7 @@ import nl.adaptivity.xmlutil.serialization.XML
|
|||
import tachiyomi.core.metadata.comicinfo.COMIC_INFO_FILE
|
||||
import tachiyomi.core.metadata.comicinfo.ComicInfo
|
||||
import tachiyomi.core.metadata.comicinfo.copyFromComicInfo
|
||||
import tachiyomi.core.metadata.comicinfo.getComicInfo
|
||||
import tachiyomi.core.metadata.tachiyomi.MangaDetails
|
||||
import tachiyomi.core.util.lang.withIOContext
|
||||
import tachiyomi.core.util.system.ImageUtil
|
||||
|
@ -122,22 +123,20 @@ actual class LocalSource(
|
|||
|
||||
// Fetch chapters of all the manga
|
||||
mangas.forEach { manga ->
|
||||
runBlocking {
|
||||
val chapters = getChapterList(manga)
|
||||
if (chapters.isNotEmpty()) {
|
||||
val chapter = chapters.last()
|
||||
val format = getFormat(chapter)
|
||||
val chapters = getChapterList(manga)
|
||||
if (chapters.isNotEmpty()) {
|
||||
val chapter = chapters.last()
|
||||
val format = getFormat(chapter)
|
||||
|
||||
if (format is Format.Epub) {
|
||||
EpubFile(format.file).use { epub ->
|
||||
epub.fillMangaMetadata(manga)
|
||||
}
|
||||
if (format is Format.Epub) {
|
||||
EpubFile(format.file).use { epub ->
|
||||
epub.fillMangaMetadata(manga)
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the cover from the first chapter found if not available
|
||||
if (manga.thumbnail_url == null) {
|
||||
updateCover(chapter, manga)
|
||||
}
|
||||
// Copy the cover from the first chapter found if not available
|
||||
if (manga.thumbnail_url == null) {
|
||||
updateCover(chapter, manga)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -153,6 +152,7 @@ actual class LocalSource(
|
|||
|
||||
// Augment manga details based on metadata files
|
||||
try {
|
||||
val mangaDir = fileSystem.getMangaDirectory(manga.url)
|
||||
val mangaDirFiles = fileSystem.getFilesInMangaDirectory(manga.url).toList()
|
||||
|
||||
val comicInfoFile = mangaDirFiles
|
||||
|
@ -169,7 +169,8 @@ actual class LocalSource(
|
|||
setMangaDetailsFromComicInfoFile(comicInfoFile.inputStream(), manga)
|
||||
}
|
||||
|
||||
// TODO: automatically convert these to ComicInfo.xml
|
||||
// Old custom JSON format
|
||||
// TODO: remove support for this entirely after a while
|
||||
legacyJsonDetailsFile != null -> {
|
||||
json.decodeFromStream<MangaDetails>(legacyJsonDetailsFile.inputStream()).run {
|
||||
title?.let { manga.title = it }
|
||||
|
@ -179,6 +180,16 @@ actual class LocalSource(
|
|||
genre?.let { manga.genre = it.joinToString() }
|
||||
status?.let { manga.status = it }
|
||||
}
|
||||
// Replace with ComicInfo.xml file
|
||||
val comicInfo = manga.getComicInfo()
|
||||
UniFile.fromFile(mangaDir)
|
||||
?.createFile(COMIC_INFO_FILE)
|
||||
?.openOutputStream()
|
||||
?.use {
|
||||
val comicInfoString = xml.encodeToString(ComicInfo.serializer(), comicInfo)
|
||||
it.write(comicInfoString.toByteArray())
|
||||
legacyJsonDetailsFile.delete()
|
||||
}
|
||||
}
|
||||
|
||||
// Copy ComicInfo.xml from chapter archive to top level if found
|
||||
|
@ -187,7 +198,6 @@ actual class LocalSource(
|
|||
.filter(Archive::isSupported)
|
||||
.toList()
|
||||
|
||||
val mangaDir = fileSystem.getMangaDirectory(manga.url)
|
||||
val folderPath = mangaDir?.absolutePath
|
||||
|
||||
val copiedFile = copyComicInfoFileFromArchive(chapterArchives, folderPath)
|
||||
|
|
|
@ -18,7 +18,7 @@ actual class LocalCoverManager(
|
|||
|
||||
actual fun find(mangaUrl: String): File? {
|
||||
return fileSystem.getFilesInMangaDirectory(mangaUrl)
|
||||
// Get all file whose names start with 'cover'
|
||||
// Get all file whose names start with "cover"
|
||||
.filter { it.isFile && it.nameWithoutExtension.equals("cover", ignoreCase = true) }
|
||||
// Get the first actual image
|
||||
.firstOrNull {
|
||||
|
|
Reference in a new issue