many new features & fixes by RSR
- Story counter & Story Source - fix: Media Quality Override
This commit is contained in:
@@ -36,10 +36,12 @@ tasks.register<GetVersionTask>("getVersion") {
|
||||
rootProject.ext.set("appVersionName", providers.gradleProperty("APP_VERSION_NAME").orElse("1.3.2").get())
|
||||
rootProject.ext.set("appVersionCode", providers.gradleProperty("APP_VERSION_CODE").orElse("272").get().toInt())
|
||||
rootProject.ext.set("applicationId", "me.eternal.purrfectsnap")
|
||||
// buildHash: when PurrfectSnap or Snapchat is updated, mappings become outdated and auto-regenerate.
|
||||
// Include version code so each release has a different hash; use random for uniqueness within same version.
|
||||
rootProject.ext.set(
|
||||
"buildHash",
|
||||
properties["debug_build_hash"]
|
||||
?: java.security.SecureRandom()
|
||||
(properties["debug_build_hash"] as String?)
|
||||
?: "${rootProject.ext["appVersionCode"]}_${java.security.SecureRandom()
|
||||
.nextLong(Long.MAX_VALUE / 1000L, Long.MAX_VALUE)
|
||||
.toString(16)
|
||||
.toString(16)}"
|
||||
)
|
||||
|
||||
@@ -1099,6 +1099,14 @@
|
||||
"name": "معلومات الوسائط السريعة في أوبرا",
|
||||
"description": "يعرض معلومات مفيدة عن الوسائط مثل تاريخ الإنشاء في قائمة سياق عارض أوبرا"
|
||||
},
|
||||
"story_counter": {
|
||||
"name": "عداد القصص",
|
||||
"description": "يعرض عداداً (مثل 1/10) عند مشاهدة القصص"
|
||||
},
|
||||
"story_source_indicator": {
|
||||
"name": "مؤشر مصدر القصة",
|
||||
"description": "يعرض أيقونة تشير إلى ما إذا كان السناب تم التقاطه من الكاميرا أو رفعه من المعرض\nيعمل فقط مع قصص الأصدقاء"
|
||||
},
|
||||
"old_bitmoji_selfie": {
|
||||
"name": "سيلفي Bitmoji القديم",
|
||||
"description": "يعيد سيلفي Bitmoji من إصدارات Snapchat القديمة"
|
||||
@@ -2767,7 +2775,6 @@
|
||||
"show_chat_edit_history": "عرض سجل تحرير الدردشة",
|
||||
"convert_message": "تحويل الرسالة"
|
||||
},
|
||||
|
||||
"chat_wallpaper_downloader": {
|
||||
"download_button": "تنزيل خلفية الدردشة"
|
||||
},
|
||||
|
||||
@@ -1099,6 +1099,14 @@
|
||||
"name": "Opera Media Quick Info",
|
||||
"description": "Shows useful information of media such as creation date in opera viewer context menu"
|
||||
},
|
||||
"story_counter": {
|
||||
"name": "Story Counter",
|
||||
"description": "Shows a counter (e.g. 1/10) when viewing stories"
|
||||
},
|
||||
"story_source_indicator": {
|
||||
"name": "Story Source Indicator",
|
||||
"description": "Shows an icon indicating whether the snap was taken from the camera or uploaded from the gallery\nOnly works with friend stories"
|
||||
},
|
||||
"old_bitmoji_selfie": {
|
||||
"name": "Old Bitmoji Selfie",
|
||||
"description": "Brings back the Bitmoji selfies from older Snapchat versions"
|
||||
@@ -2339,16 +2347,16 @@
|
||||
"custom_android_id": {
|
||||
"null": "Use real Android ID"
|
||||
},
|
||||
"add_friend_source_spoof": {
|
||||
"added_by_username": "By Username",
|
||||
"added_by_mention": "By Mention",
|
||||
"added_by_group_chat": "By Group Chat",
|
||||
"added_by_qr_code": "By QR Code",
|
||||
"added_by_community": "By Community",
|
||||
"added_by_quick_add": "By Quick Add (high risk of being banned)",
|
||||
"added_by_spotlight": "By Spotlight",
|
||||
"null": "Don't spoof source"
|
||||
},
|
||||
"add_friend_source_spoof": {
|
||||
"added_by_username": "By Username",
|
||||
"added_by_mention": "By Mention",
|
||||
"added_by_group_chat": "By Group Chat",
|
||||
"added_by_qr_code": "By QR Code",
|
||||
"added_by_community": "By Community",
|
||||
"added_by_quick_add": "By Quick Add (high risk of being banned)",
|
||||
"added_by_spotlight": "By Spotlight",
|
||||
"null": "Don't spoof source"
|
||||
},
|
||||
"custom_streaks_expiration_format": {
|
||||
"null": "System Default"
|
||||
},
|
||||
@@ -2766,7 +2774,6 @@
|
||||
"show_chat_edit_history": "Show Chat Edit History",
|
||||
"convert_message": "Convert Message"
|
||||
},
|
||||
|
||||
"chat_wallpaper_downloader": {
|
||||
"download_button": "Download Chat Wallpaper"
|
||||
},
|
||||
|
||||
@@ -50,6 +50,8 @@ class UserInterfaceTweaks : ConfigContainer() {
|
||||
"hide_map_reactions",
|
||||
) { requireRestart(); versionCheck = RES_OBF_VERSION_CHECK }
|
||||
val operaMediaQuickInfo = boolean("opera_media_quick_info") { requireRestart() }
|
||||
val storyCounter = boolean("story_counter") { requireRestart() }
|
||||
val storySourceIndicator = boolean("story_source_indicator") { requireRestart() }
|
||||
val oldBitmojiSelfie = unique("old_bitmoji_selfie", "2d", "3d") { requireCleanCache() }
|
||||
val disableSpotlight = boolean("disable_spotlight") { requireRestart() }
|
||||
val verticalStoryViewer = boolean("vertical_story_viewer") { requireRestart() }
|
||||
|
||||
@@ -87,6 +87,7 @@ class FeatureManager(
|
||||
AutoSave(),
|
||||
AutoReply(),
|
||||
UITweaks(),
|
||||
OperaStoryCounter(),
|
||||
ConfigurationOverride(),
|
||||
COFOverride(),
|
||||
UnsaveableMessages(),
|
||||
|
||||
@@ -47,6 +47,39 @@ class ConfigurationOverride : Feature("Configuration Override") {
|
||||
overrideProperty("TRANSCODING_MAX_QUALITY", { context.config.global.mediaUploadQualityConfig.forceVideoUploadSourceQuality.get() },
|
||||
{ true }, isAppExperiment = true)
|
||||
|
||||
run {
|
||||
val isForceQuality = { _: ConfigKeyInfo -> context.config.global.mediaUploadQualityConfig.forceVideoUploadSourceQuality.get() }
|
||||
val level7Value = { _: ConfigKeyInfo -> 700 }
|
||||
arrayOf(
|
||||
"MY_STORY_UPLOAD_QUALITY_LEVEL",
|
||||
"PUBLIC_STORY_UPLOAD_QUALITY_LEVEL",
|
||||
"GROUP_STORY_UPLOAD_QUALITY_LEVEL",
|
||||
"SPOTLIGHT_UPLOAD_QUALITY_LEVEL",
|
||||
"MEDIA_UPLOAD_QUALITY_LEVEL",
|
||||
"MESSAGING_MEDIA_UPLOAD_QUALITY_LEVEL",
|
||||
"IMAGE_UPLOAD_QUALITY_LEVEL",
|
||||
"IMAGE_QUALITY_LEVEL_FOR_PRE_UPLOAD",
|
||||
"IMAGE_QUALITY_LEVEL_FOR_PUBLIC_POSTING",
|
||||
"IMAGE_QUALITY_LEVEL_FOR_PRIVATE_POSTING",
|
||||
"RETRANSCODE_UPLOAD_QUALITY_LEVEL",
|
||||
"MEMORIES_BACKUP_MEDIA_LEVEL",
|
||||
"MEMORIES_BACKUP_MEDIA_LEVEL_HIGH_QUALITY",
|
||||
"MEDIA_EXPORT_QUALITY_LEVEL"
|
||||
).forEach { key ->
|
||||
overrideProperty(key, isForceQuality, level7Value)
|
||||
}
|
||||
overrideProperty("ENABLE_HIGH_QUALITY_MEMORIES_BACKUP",
|
||||
isForceQuality, { true })
|
||||
overrideProperty("MEDIA_QUALITY_LEVEL_DOWNGRADING_PERCENTAGE",
|
||||
isForceQuality, { 0.0f })
|
||||
}
|
||||
|
||||
run {
|
||||
val isDisableCompression = { _: ConfigKeyInfo -> context.config.global.mediaUploadQualityConfig.disableImageCompression.get() }
|
||||
overrideProperty("LIBJPEG_IMAGE_ENCODING_QUALITY", isDisableCompression, { 100 })
|
||||
overrideProperty("LIBJPEG_IMAGE_ENCODING_QUALITY_V2", isDisableCompression, { 100 })
|
||||
}
|
||||
|
||||
overrideProperty("CAMERA_ME_ENABLE_HEVC_RECORDING", { context.config.camera.hevcRecording.get() },
|
||||
{ true })
|
||||
overrideProperty("MEDIA_RECORDER_MAX_QUALITY_LEVEL", { context.config.camera.forceCameraSourceEncoding.get() },
|
||||
|
||||
@@ -11,22 +11,34 @@ class MediaUploadQualityOverride : Feature("Media Upload Quality Override") {
|
||||
override fun init() {
|
||||
if (context.config.global.mediaUploadQualityConfig.forceVideoUploadSourceQuality.get()) {
|
||||
context.mappings.useMapper(MediaQualityLevelProviderMapper::class) {
|
||||
mediaQualityLevelProvider.getAsClass()?.hook(
|
||||
mediaQualityLevelProviderMethod.getAsString()!!,
|
||||
HookStage.AFTER
|
||||
val providerClass = mediaQualityLevelProvider.getAsClass()
|
||||
val providerMethodName = mediaQualityLevelProviderMethod.getAsString()
|
||||
|
||||
if (providerClass == null || providerMethodName == null) {
|
||||
context.log.warn("MediaQualityLevelProvider mapping failed - provider class or method not found")
|
||||
return@useMapper
|
||||
}
|
||||
|
||||
providerClass.hook(
|
||||
providerMethodName,
|
||||
HookStage.BEFORE
|
||||
) { param ->
|
||||
val method = param.method() as Method
|
||||
val enumClass = method.returnType
|
||||
val values = enumClass.enumConstants ?: return@hook
|
||||
val nameMethod = runCatching { enumClass.getMethod("name") }.getOrNull()
|
||||
val maxIndex = values.indexOfFirst { nameMethod?.invoke(it)?.toString() == "LEVEL_MAX" }
|
||||
val targetIndex = when {
|
||||
maxIndex > 0 -> maxIndex - 1
|
||||
values.size > 1 -> values.lastIndex - 1
|
||||
else -> return@hook
|
||||
try {
|
||||
val method = param.method() as Method
|
||||
val returnType = method.returnType
|
||||
|
||||
val levelMax = returnType.enumConstants?.firstOrNull {
|
||||
it.toString() == "LEVEL_MAX"
|
||||
}
|
||||
|
||||
if (levelMax != null) {
|
||||
param.setArg(1, levelMax)
|
||||
} else {
|
||||
context.log.warn("LEVEL_MAX enum constant not found. Available: ${returnType.enumConstants?.joinToString()}")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
context.log.error("Failed to override video quality", e)
|
||||
}
|
||||
val target = values[targetIndex]
|
||||
if (param.getResult() != target) param.setResult(target)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,23 +48,41 @@ class MediaUploadQualityOverride : Feature("Media Upload Quality Override") {
|
||||
|
||||
if (imageUploadFormat != null || disableImageCompression) {
|
||||
Bitmap::class.java.hook("compress", HookStage.BEFORE) { param ->
|
||||
if (param.arg<Int>(1) == 0) return@hook
|
||||
if (param.arg<Any>(0) == Bitmap.CompressFormat.JPEG) {
|
||||
@Suppress("DEPRECATION")
|
||||
param.setArg(0, when (imageUploadFormat) {
|
||||
"png" -> Bitmap.CompressFormat.PNG
|
||||
"webp" -> Bitmap.CompressFormat.WEBP
|
||||
"jpeg" -> Bitmap.CompressFormat.JPEG
|
||||
else -> Bitmap.CompressFormat.JPEG
|
||||
})
|
||||
if (disableImageCompression) {
|
||||
param.setArg(1, 100)
|
||||
try {
|
||||
val quality = param.arg<Int>(1)
|
||||
val currentFormat = param.arg<Any>(0)
|
||||
|
||||
if (quality == 0) return@hook
|
||||
|
||||
if (currentFormat == Bitmap.CompressFormat.JPEG ||
|
||||
currentFormat == Bitmap.CompressFormat.PNG ||
|
||||
currentFormat == Bitmap.CompressFormat.WEBP) {
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
val newFormat = when (imageUploadFormat) {
|
||||
"png" -> Bitmap.CompressFormat.PNG
|
||||
"webp" -> Bitmap.CompressFormat.WEBP
|
||||
"jpeg" -> Bitmap.CompressFormat.JPEG
|
||||
else -> currentFormat as Bitmap.CompressFormat
|
||||
}
|
||||
|
||||
param.setArg(0, newFormat)
|
||||
|
||||
if (disableImageCompression) {
|
||||
param.setArg(1, 100)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
context.log.error("Failed to override image compression", e)
|
||||
}
|
||||
}
|
||||
|
||||
findClass("com.snap.camera.jni.SnapImageTranscoder").hook("nativeEncodeBitmapToJpeg", HookStage.BEFORE) {
|
||||
it.setResult(ByteArray(0))
|
||||
try {
|
||||
findClass("com.snap.camera.jni.SnapImageTranscoder").hook("nativeEncodeBitmapToJpeg", HookStage.BEFORE) {
|
||||
it.setResult(ByteArray(0))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
context.log.warn("Failed to hook SnapImageTranscoder - this is expected on some Snapchat versions")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
package me.eternal.purrfectsnap.core.features.impl.ui
|
||||
|
||||
import android.view.Gravity
|
||||
import android.widget.FrameLayout
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.CameraAlt
|
||||
import androidx.compose.material.icons.outlined.PhotoLibrary
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import me.eternal.purrfectsnap.common.ui.createComposeView
|
||||
import me.eternal.purrfectsnap.core.event.events.impl.AddViewEvent
|
||||
import me.eternal.purrfectsnap.core.features.Feature
|
||||
import me.eternal.purrfectsnap.core.ui.children
|
||||
import me.eternal.purrfectsnap.core.util.hook.HookStage
|
||||
import me.eternal.purrfectsnap.core.util.hook.hook
|
||||
import me.eternal.purrfectsnap.core.util.ktx.getObjectField
|
||||
import me.eternal.purrfectsnap.core.wrapper.impl.media.opera.Layer
|
||||
import me.eternal.purrfectsnap.core.wrapper.impl.media.opera.ParamMap
|
||||
import me.eternal.purrfectsnap.mapper.impl.OperaPageViewControllerMapper
|
||||
|
||||
class OperaStoryCounter : Feature("OperaStoryCounter") {
|
||||
private val counterState = mutableStateOf("")
|
||||
// "CAMERA" or "GALLERY" or ""
|
||||
private val sourceState = mutableStateOf("")
|
||||
|
||||
override fun init() {
|
||||
val showCounter = this@OperaStoryCounter.context.config.userInterface.storyCounter.get()
|
||||
val showSourceIndicator = this@OperaStoryCounter.context.config.userInterface.storySourceIndicator.get()
|
||||
|
||||
if (!showCounter && !showSourceIndicator) return
|
||||
|
||||
this@OperaStoryCounter.context.event.subscribe(AddViewEvent::class) { event ->
|
||||
if (event.view is FrameLayout && event.parent.javaClass.superclass?.name?.endsWith("OpenLayout") == true) {
|
||||
val viewGroup = event.view as FrameLayout
|
||||
|
||||
if (viewGroup.findViewWithTag<android.view.View>("story_counter") != null ||
|
||||
event.parent.findViewWithTag<android.view.View>("story_counter") != null) return@subscribe
|
||||
|
||||
if (event.parent.children().none { it.javaClass.name.endsWith("ScalableCircleMaskFrameLayout") }) return@subscribe
|
||||
|
||||
val composeView = createComposeView(viewGroup.context) {
|
||||
val counterText = counterState.value
|
||||
val source = sourceState.value
|
||||
val hasCounter = showCounter && counterText.isNotEmpty()
|
||||
val hasSource = showSourceIndicator && source.isNotEmpty()
|
||||
|
||||
if (hasCounter || hasSource) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(
|
||||
color = Color(0x4C000000),
|
||||
shape = androidx.compose.foundation.shape.CircleShape
|
||||
)
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(horizontal = 10.dp, vertical = 4.dp)
|
||||
) {
|
||||
// Counter section
|
||||
if (hasCounter) {
|
||||
Text(
|
||||
text = counterText,
|
||||
color = Color.White,
|
||||
fontSize = 11.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
)
|
||||
}
|
||||
|
||||
// Divider between counter and source icon
|
||||
if (hasCounter && hasSource) {
|
||||
Spacer(modifier = Modifier.width(6.dp))
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(1.dp)
|
||||
.height(14.dp)
|
||||
.background(Color.White.copy(alpha = 0.5f))
|
||||
)
|
||||
Spacer(modifier = Modifier.width(6.dp))
|
||||
}
|
||||
|
||||
// Source icon section
|
||||
if (hasSource) {
|
||||
val icon = if (source == "CAMERA") Icons.Outlined.CameraAlt else Icons.Outlined.PhotoLibrary
|
||||
val description = if (source == "CAMERA") "Camera" else "Gallery"
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = description,
|
||||
tint = Color.White,
|
||||
modifier = Modifier.size(14.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}.apply {
|
||||
tag = "story_counter"
|
||||
layoutParams = FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.WRAP_CONTENT,
|
||||
FrameLayout.LayoutParams.WRAP_CONTENT
|
||||
).apply {
|
||||
gravity = Gravity.TOP or Gravity.END
|
||||
topMargin = this@OperaStoryCounter.context.userInterface.dpToPx(50)
|
||||
marginEnd = this@OperaStoryCounter.context.userInterface.dpToPx(10)
|
||||
}
|
||||
}
|
||||
viewGroup.addView(composeView)
|
||||
}
|
||||
}
|
||||
|
||||
onNextActivityCreate {
|
||||
this@OperaStoryCounter.context.mappings.useMapper(OperaPageViewControllerMapper::class) {
|
||||
arrayOf(onDisplayStateChange, onDisplayStateChangeGesture).forEach { methodName ->
|
||||
classReference.get()?.hook(
|
||||
methodName.get() ?: return@forEach,
|
||||
HookStage.AFTER
|
||||
) { param ->
|
||||
val viewState = (param.thisObject() as Any).getObjectField(viewStateField.get()!!).toString()
|
||||
|
||||
if (viewState != "FULLY_DISPLAYED") {
|
||||
return@hook
|
||||
}
|
||||
|
||||
val operaLayerList = (param.thisObject() as Any).getObjectField(layerListField.get()!!) as ArrayList<*>
|
||||
val mediaParamMap: ParamMap = operaLayerList.map { Layer(it) }.first().paramMap
|
||||
val snapSource = mediaParamMap["SNAP_SOURCE"]?.toString()
|
||||
|
||||
// Don't show counter in conversation chats or groups
|
||||
if (mediaParamMap.containsKey("MESSAGE_ID")) {
|
||||
this@OperaStoryCounter.context.runOnUiThread {
|
||||
counterState.value = ""
|
||||
sourceState.value = ""
|
||||
}
|
||||
return@hook
|
||||
}
|
||||
|
||||
// Don't show counter on Spotlight (single snap, no story context)
|
||||
if (snapSource == "SINGLE_SNAP_STORY") {
|
||||
this@OperaStoryCounter.context.runOnUiThread {
|
||||
counterState.value = ""
|
||||
sourceState.value = ""
|
||||
}
|
||||
return@hook
|
||||
}
|
||||
|
||||
// Extract counter info
|
||||
val currentIndex = mediaParamMap["snap_index_in_story"]?.toString()?.toIntOrNull()
|
||||
?: mediaParamMap["SNAP_POSITION_IN_STORY"]?.toString()?.toIntOrNull()
|
||||
val totalCount = mediaParamMap["snap_story_length"]?.toString()?.toIntOrNull()
|
||||
?: mediaParamMap["NUM_SNAPS_IN_STORY"]?.toString()?.toIntOrNull()
|
||||
|
||||
// Extract media origin from PLAYABLE_STORY_SNAP_RECORD
|
||||
var mediaOrigin = ""
|
||||
if (showSourceIndicator) {
|
||||
val snapRecord = mediaParamMap["PLAYABLE_STORY_SNAP_RECORD"]?.toString() ?: ""
|
||||
mediaOrigin = if (snapRecord.contains("mediaOrigins=")) {
|
||||
if (snapRecord.contains("mediaOrigins=[CAMERA]")) "CAMERA" else "GALLERY"
|
||||
} else ""
|
||||
}
|
||||
|
||||
this@OperaStoryCounter.context.runOnUiThread {
|
||||
counterState.value = if (showCounter && currentIndex != null && totalCount != null && totalCount > 0) {
|
||||
"${currentIndex + 1} / $totalCount"
|
||||
} else ""
|
||||
sourceState.value = mediaOrigin
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package me.eternal.purrfectsnap.mapper.impl
|
||||
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import me.eternal.purrfectsnap.mapper.AbstractClassMapper
|
||||
import me.eternal.purrfectsnap.mapper.ext.findConstString
|
||||
import me.eternal.purrfectsnap.mapper.ext.getClassName
|
||||
import me.eternal.purrfectsnap.mapper.ext.hasStaticConstructorString
|
||||
import me.eternal.purrfectsnap.mapper.ext.isAbstract
|
||||
@@ -32,12 +33,60 @@ class MediaQualityLevelProviderMapper : AbstractClassMapper("MediaQualityLevelPr
|
||||
if (!clazz.isAbstract()) continue
|
||||
if (clazz.fields.none { it.accessFlags and AccessFlags.TRANSIENT.value != 0 }) continue
|
||||
|
||||
clazz.methods.firstOrNull { it.returnType == "L$enumQualityLevel;" }?.let {
|
||||
clazz.methods.firstOrNull { method ->
|
||||
method.returnType == "L$enumQualityLevel;"
|
||||
&& method.parameters.size == 2
|
||||
&& method.parameters.last().type == "L$enumQualityLevel;"
|
||||
}?.let {
|
||||
mediaQualityLevelProvider.set(clazz.getClassName())
|
||||
mediaQualityLevelProviderMethod.set(it.name)
|
||||
return@mapper
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mapper {
|
||||
if (enumQualityLevel == null || mediaQualityLevelProvider.get() != null) return@mapper
|
||||
|
||||
for (clazz in classes) {
|
||||
if (clazz.methods.none { it.implementation?.findConstString("video-transcoding-level-", contains = true) == true }) continue
|
||||
|
||||
clazz.methods.firstOrNull { method ->
|
||||
method.returnType == "L$enumQualityLevel;"
|
||||
&& method.parameters.size == 2
|
||||
&& method.parameters.last().type == "L$enumQualityLevel;"
|
||||
}?.let {
|
||||
mediaQualityLevelProvider.set(clazz.getClassName())
|
||||
mediaQualityLevelProviderMethod.set(it.name)
|
||||
return@mapper
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mapper {
|
||||
if (enumQualityLevel == null || mediaQualityLevelProvider.get() != null) return@mapper
|
||||
|
||||
for (clazz in classes) {
|
||||
val hasTranscodingConfig = clazz.methods.any { method ->
|
||||
method.implementation?.findConstString("video-transcoding-level-1", contains = true) == true
|
||||
}
|
||||
if (!hasTranscodingConfig) continue
|
||||
|
||||
val hasImageTranscodingConfig = clazz.methods.any { method ->
|
||||
method.implementation?.findConstString("image_transcoding_level_", contains = true) == true
|
||||
}
|
||||
if (!hasImageTranscodingConfig) continue
|
||||
|
||||
clazz.methods.firstOrNull { method ->
|
||||
method.returnType == "L$enumQualityLevel;"
|
||||
&& method.parameters.size == 2
|
||||
&& method.parameters.last().type == "L$enumQualityLevel;"
|
||||
}?.let { qualityMethod ->
|
||||
mediaQualityLevelProvider.set(clazz.getClassName())
|
||||
mediaQualityLevelProviderMethod.set(qualityMethod.name)
|
||||
return@mapper
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,6 +294,10 @@ val syncTasks = cargoTargets.mapIndexed { index, target ->
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure checksums dir exists at configuration time so Gradle 9 input validation passes
|
||||
// (it is populated by syncTasks' doLast)
|
||||
layout.buildDirectory.dir("checksums").get().asFile.mkdirs()
|
||||
|
||||
val generateChecksumsFile = tasks.register("generateChecksumsFile") {
|
||||
dependsOn(syncTasks)
|
||||
val generatedDir = layout.buildDirectory.dir("generated/source/checksums/kotlin")
|
||||
|
||||
Reference in New Issue
Block a user