From 87ca0eaa824ebbf430ce82fd4c2f5b1193c3cb63 Mon Sep 17 00:00:00 2001 From: RSR/ <52901789+RSR1337@users.noreply.github.com> Date: Wed, 4 Mar 2026 07:28:26 +0400 Subject: [PATCH] fix: Media Quality --- .../features/impl/ConfigurationOverride.kt | 33 ++++++++ .../impl/global/MediaUploadQualityOverride.kt | 84 +++++++++++++------ .../impl/MediaQualityLevelProviderMapper.kt | 51 ++++++++++- 3 files changed, 140 insertions(+), 28 deletions(-) diff --git a/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/ConfigurationOverride.kt b/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/ConfigurationOverride.kt index cb507aa8..b7996471 100644 --- a/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/ConfigurationOverride.kt +++ b/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/ConfigurationOverride.kt @@ -48,6 +48,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() }, diff --git a/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/global/MediaUploadQualityOverride.kt b/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/global/MediaUploadQualityOverride.kt index 4a2a4ab1..204a5a4d 100644 --- a/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/global/MediaUploadQualityOverride.kt +++ b/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/global/MediaUploadQualityOverride.kt @@ -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(1) == 0) return@hook - if (param.arg(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(1) + val currentFormat = param.arg(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") } } } diff --git a/mapper/src/main/kotlin/me/eternal/purrfectsnap/mapper/impl/MediaQualityLevelProviderMapper.kt b/mapper/src/main/kotlin/me/eternal/purrfectsnap/mapper/impl/MediaQualityLevelProviderMapper.kt index 33a95e43..40944442 100644 --- a/mapper/src/main/kotlin/me/eternal/purrfectsnap/mapper/impl/MediaQualityLevelProviderMapper.kt +++ b/mapper/src/main/kotlin/me/eternal/purrfectsnap/mapper/impl/MediaQualityLevelProviderMapper.kt @@ -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 + } + } + } } }