Comick: Option cover quality (#10085)

* Comick: Option cover quality

i think need some text suggestion

use cover quality served by the site by default on browse. this can save lot of bandwidth compared to original 2x-100x smaller. thats why i think can increase rate limit  a little bit.

add option to choose cover quality when opening the manga

closes #9088
closes #2550

* update version

* fix first vol cover, renaming

* cleaning, already default value

* applied to first vol cover as well

* change default value

* rename

* Update messages_en.properties
This commit is contained in:
Luqman 2025-08-14 21:34:04 +07:00 committed by Draff
parent 485f66bad8
commit f6d2fd3c65
Signed by: Draff
GPG Key ID: E8A89F3211677653
5 changed files with 62 additions and 7 deletions

View File

@ -22,6 +22,10 @@ score_position_top=Top
score_position_middle=Middle
score_position_bottom=Bottom
score_position_none=Hide Score
cover_quality_title=Cover Quality
cover_quality_original=Original
cover_quality_compressed=Compressed
cover_quality_web_default=Small (Web Default)
chapter_score_filtering_title=Automatically de-duplicate chapters
chapter_score_filtering_on=For each chapter, only displays the scanlator with the highest score
chapter_score_filtering_off=Does not filterout any chapters based on score (any other scanlator filtering will still apply)

View File

@ -1,7 +1,7 @@
ext {
extName = 'Comick'
extClass = '.ComickFactory'
extVersionCode = 58
extVersionCode = 59
isNsfw = true
}

View File

@ -141,6 +141,29 @@ abstract class Comick(
}
}.also(screen::addPreference)
ListPreference(screen.context).apply {
key = COVER_QUALITY_PREF
title = intl["cover_quality_title"]
entries = arrayOf(
intl["cover_quality_original"],
intl["cover_quality_compressed"],
intl["cover_quality_web_default"],
)
entryValues = arrayOf(
"Original",
"Compressed",
COVER_QUALITY_DEFAULT,
)
setDefaultValue(COVER_QUALITY_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
preferences.edit()
.putString(COVER_QUALITY_PREF, newValue as String)
.commit()
}
}.also(screen::addPreference)
SwitchPreferenceCompat(screen.context).apply {
key = LOCAL_TITLE_PREF
title = intl["local_title_title"]
@ -224,6 +247,11 @@ abstract class Comick(
private val SharedPreferences.updateCover: Boolean
get() = getBoolean(FIRST_COVER_PREF, FIRST_COVER_DEFAULT)
private val coverQuality: CoverQuality
get() = CoverQuality.valueOf(
preferences.getString(COVER_QUALITY_PREF, COVER_QUALITY_DEFAULT) ?: COVER_QUALITY_DEFAULT,
)
private val SharedPreferences.localTitle: String
get() = if (getBoolean(
LOCAL_TITLE_PREF,
@ -253,7 +281,7 @@ abstract class Comick(
.build()
private val imageClient = network.cloudflareClient.newBuilder()
.rateLimit(12, 8, TimeUnit.SECONDS) // == 1.5req/1sec == 3req/2sec == 90req/60sec
.rateLimit(14, 8, TimeUnit.SECONDS) // == 1.75req/1sec == 7req/4sec == 105req/60sec
.build()
private fun imageInterceptor(chain: Interceptor.Chain): Response {
@ -519,6 +547,7 @@ abstract class Comick(
covers = localCovers.ifEmpty { originalCovers }.ifEmpty { firstVol },
groupTags = preferences.groupTags,
titleLang = preferences.localTitle,
coverQuality = coverQuality,
)
}
return mangaData.toSManga(
@ -527,6 +556,7 @@ abstract class Comick(
showAlternativeTitles = preferences.showAlternativeTitles,
groupTags = preferences.groupTags,
titleLang = preferences.localTitle,
coverQuality = coverQuality,
)
}
@ -663,6 +693,8 @@ abstract class Comick(
private const val MIGRATED_IGNORED_GROUPS = "MigratedIgnoredGroups"
private const val FIRST_COVER_PREF = "DefaultCover"
private const val FIRST_COVER_DEFAULT = true
private const val COVER_QUALITY_PREF = "CoverQuality"
const val COVER_QUALITY_DEFAULT = "WebDefault"
private const val SCORE_POSITION_PREF = "ScorePosition"
const val SCORE_POSITION_DEFAULT = "top"
private const val LOCAL_TITLE_PREF = "LocalTitle"

View File

@ -41,6 +41,7 @@ class Manga(
covers: List<MDcovers>? = null,
groupTags: Boolean = GROUP_TAGS_DEFAULT,
titleLang: String,
coverQuality: CoverQuality = CoverQuality.Compressed,
): SManga {
val entryTitle = comic.altTitles.firstOrNull {
titleLang != "all" && !it.lang.isNullOrBlank() && titleLang.startsWith(it.lang)
@ -82,6 +83,7 @@ class Manga(
thumbnail_url = parseCover(
comic.cover,
covers ?: comic.mdCovers,
coverQuality,
)
artist = artists.joinToString { it.name.trim() }
author = authors.joinToString { it.name.trim() }

View File

@ -39,13 +39,30 @@ internal fun Int?.parseStatus(translationComplete: Boolean?): Int {
else -> SManga.UNKNOWN
}
}
enum class CoverQuality {
Original, // HQ original
Compressed, // HQ but compressed
WebDefault, // what comick serves in browser, usually compressed + downscaled
}
internal fun parseCover(thumbnailUrl: String?, mdCovers: List<MDcovers>): String? {
val b2key = mdCovers.firstOrNull()?.b2key
?: return thumbnailUrl
val vol = mdCovers.firstOrNull()?.vol.orEmpty()
internal fun parseCover(thumbnailUrl: String?, mdCovers: List<MDcovers>, coverQuality: CoverQuality = CoverQuality.WebDefault): String? {
fun addOrReplaceCoverQualitySuffix(url: String, qualitySuffix: String): String {
return url.substringBeforeLast('.').replace(Regex("-(m|s)$"), "") +
"$qualitySuffix.jpg#${url.substringAfter('#', "")}"
}
return thumbnailUrl?.replaceAfterLast("/", "$b2key#$vol")
val mdCover = mdCovers.firstOrNull()
val coverUrl = if (mdCover != null) {
thumbnailUrl?.replaceAfterLast("/", "${mdCover.b2key}#${mdCover.vol.orEmpty()}")
} else {
thumbnailUrl
} ?: return null
return when (coverQuality) {
CoverQuality.Original -> coverUrl
CoverQuality.Compressed -> addOrReplaceCoverQualitySuffix(coverUrl, "-m")
CoverQuality.WebDefault -> addOrReplaceCoverQualitySuffix(coverUrl, "-s")
}
}
internal fun beautifyChapterName(vol: String, chap: String, title: String): String {