commit 62ff1d998f2b00da17b2c1fe6038b549a8e3718f
parent fad13ee7a26acb8dad7b892927afe9c33f439d16
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date: Tue, 14 May 2024 21:28:57 +0200
feat: media upload quality override
Diffstat:
6 files changed, 79 insertions(+), 31 deletions(-)
diff --git a/common/src/main/assets/lang/en_US.json b/common/src/main/assets/lang/en_US.json
@@ -686,6 +686,24 @@
"name": "Snapchat Plus",
"description": "Enables Snapchat Plus features\nSome Server-sided features may not work"
},
+ "media_upload_quality": {
+ "name": "Media Upload Quality",
+ "description": "Overrides the media upload quality",
+ "properties": {
+ "force_video_upload_source_quality": {
+ "name": "Force Video Upload Source Quality",
+ "description": "Forces Snapchat to use the source quality when uploading videos\nPlease note that this may not remove metadata from media"
+ },
+ "disable_image_compression": {
+ "name": "Disable Image Compression",
+ "description": "Disables image compression when uploading media"
+ },
+ "custom_image_upload_format": {
+ "name": "Custom Image Upload Format",
+ "description": "Sets a custom image upload format\nSelect a lossless format (like PNG) for the best quality"
+ }
+ }
+ },
"disable_confirmation_dialogs": {
"name": "Disable Confirmation Dialogs",
"description": "Automatically confirms selected actions"
@@ -738,10 +756,6 @@
"name": "Disable Google Play Services Dialogs",
"description": "Prevent Google Play Services availability dialogs from being shown"
},
- "force_upload_source_quality": {
- "name": "Force Upload Source Quality",
- "description": "Forces Snapchat to upload media in the original quality\nPlease note that this may not remove metadata from media"
- },
"default_volume_controls": {
"name": "Default Volume Controls",
"description": "Forces Snapchat to use system volume controls"
diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/Global.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/Global.kt
@@ -1,6 +1,7 @@
package me.rhunk.snapenhance.common.config.impl
import me.rhunk.snapenhance.common.config.ConfigContainer
+import me.rhunk.snapenhance.common.config.ConfigFlag
import me.rhunk.snapenhance.common.config.FeatureNotice
class Global : ConfigContainer() {
@@ -27,8 +28,16 @@ class Global : ConfigContainer() {
val spoofBatteryLevel = string("spoof_battery_level") { requireRestart(); inputCheck = { it.isEmpty() || it.toIntOrNull() in 0..100 } }
val spoofHeadphones = boolean("spoof_headphones") { requireRestart() }
}
+
+ inner class MediaUploadQualityConfig : ConfigContainer() {
+ val forceVideoUploadSourceQuality = boolean("force_video_upload_source_quality") { requireRestart() }
+ val disableImageCompression = boolean("disable_image_compression") { requireRestart() }
+ val customUploadImageFormat = unique("custom_image_upload_format", "jpeg", "png", "webp") { requireRestart(); addFlags(ConfigFlag.NO_TRANSLATE) }
+ }
+
val betterLocation = container("better_location", BetterLocation())
val snapchatPlus = boolean("snapchat_plus") { requireRestart() }
+ val mediaUploadQualityConfig = container("media_upload_quality", MediaUploadQualityConfig())
val disableConfirmationDialogs = multiple("disable_confirmation_dialogs", "erase_message", "remove_friend", "block_friend", "ignore_friend", "hide_friend", "hide_conversation", "clear_conversation") { requireRestart() }
val disableMetrics = boolean("disable_metrics") { requireRestart() }
val disableStorySections = multiple("disable_story_sections", "friends", "following", "discover") { requireRestart(); requireCleanCache() }
@@ -42,7 +51,6 @@ class Global : ConfigContainer() {
val defaultVideoPlaybackRate = float("default_video_playback_rate", 1.0F) { requireRestart(); inputCheck = { (it.toFloatOrNull() ?: 1.0F) in 0.1F..4.0F} }
val videoPlaybackRateSlider = boolean("video_playback_rate_slider") { requireRestart() }
val disableGooglePlayDialogs = boolean("disable_google_play_dialogs") { requireRestart() }
- val forceUploadSourceQuality = boolean("force_upload_source_quality") { requireRestart() }
val defaultVolumeControls = boolean("default_volume_controls") { requireRestart() }
val hideActiveMusic = boolean("hide_active_music") { requireRestart() }
val disableSnapSplitting = boolean("disable_snap_splitting") { addNotices(FeatureNotice.INTERNAL_BEHAVIOR) }
diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/FeatureManager.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/FeatureManager.kt
@@ -79,7 +79,7 @@ class FeatureManager(
SendOverride(),
UnlimitedSnapViewTime(),
BypassVideoLengthRestriction(),
- MediaQualityLevelOverride(),
+ MediaUploadQualityOverride(),
MeoPasscodeBypass(),
AppLock(),
CameraTweaks(),
diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/ConfigurationOverride.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/ConfigurationOverride.kt
@@ -45,7 +45,7 @@ class ConfigurationOverride : Feature("Configuration Override", loadParams = Fea
overrideProperty("STREAK_EXPIRATION_INFO", { context.config.userInterface.streakExpirationInfo.get() },
{ true })
- overrideProperty("TRANSCODING_MAX_QUALITY", { context.config.global.forceUploadSourceQuality.get() },
+ overrideProperty("TRANSCODING_MAX_QUALITY", { context.config.global.mediaUploadQualityConfig.forceVideoUploadSourceQuality.get() },
{ true }, isAppExperiment = true)
overrideProperty("CAMERA_ME_ENABLE_HEVC_RECORDING", { context.config.camera.hevcRecording.get() },
diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/global/MediaQualityLevelOverride.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/global/MediaQualityLevelOverride.kt
@@ -1,23 +0,0 @@
-package me.rhunk.snapenhance.core.features.impl.global
-
-import me.rhunk.snapenhance.core.features.Feature
-import me.rhunk.snapenhance.core.features.FeatureLoadParams
-import me.rhunk.snapenhance.core.util.hook.HookStage
-import me.rhunk.snapenhance.core.util.hook.hook
-import me.rhunk.snapenhance.mapper.impl.MediaQualityLevelProviderMapper
-import java.lang.reflect.Method
-
-class MediaQualityLevelOverride : Feature("MediaQualityLevelOverride", loadParams = FeatureLoadParams.INIT_SYNC) {
- override fun init() {
- if (!context.config.global.forceUploadSourceQuality.get()) return
-
- context.mappings.useMapper(MediaQualityLevelProviderMapper::class) {
- mediaQualityLevelProvider.getAsClass()?.hook(
- mediaQualityLevelProviderMethod.getAsString()!!,
- HookStage.BEFORE
- ) { param ->
- param.setResult((param.method() as Method).returnType.enumConstants.firstOrNull { it.toString() == "LEVEL_MAX" } )
- }
- }
- }
-}-
\ No newline at end of file
diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/global/MediaUploadQualityOverride.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/global/MediaUploadQualityOverride.kt
@@ -0,0 +1,49 @@
+package me.rhunk.snapenhance.core.features.impl.global
+
+import android.graphics.Bitmap
+import me.rhunk.snapenhance.core.features.Feature
+import me.rhunk.snapenhance.core.features.FeatureLoadParams
+import me.rhunk.snapenhance.core.util.hook.HookStage
+import me.rhunk.snapenhance.core.util.hook.hook
+import me.rhunk.snapenhance.mapper.impl.MediaQualityLevelProviderMapper
+import java.lang.reflect.Method
+
+class MediaUploadQualityOverride : Feature("Media Upload Quality Override", loadParams = FeatureLoadParams.INIT_SYNC) {
+ override fun init() {
+ if (context.config.global.mediaUploadQualityConfig.forceVideoUploadSourceQuality.get()) {
+ context.mappings.useMapper(MediaQualityLevelProviderMapper::class) {
+ mediaQualityLevelProvider.getAsClass()?.hook(
+ mediaQualityLevelProviderMethod.getAsString()!!,
+ HookStage.BEFORE
+ ) { param ->
+ param.setResult((param.method() as Method).returnType.enumConstants.firstOrNull { it.toString() == "LEVEL_MAX" } )
+ }
+ }
+ }
+
+ val disableImageCompression by context.config.global.mediaUploadQualityConfig.disableImageCompression
+ val imageUploadFormat = context.config.global.mediaUploadQualityConfig.customUploadImageFormat.getNullable()
+
+ 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)
+ }
+ }
+ }
+
+ findClass("com.snap.camera.jni.SnapImageTranscoder").hook("nativeEncodeBitmapToJpeg", HookStage.BEFORE) {
+ it.setResult(ByteArray(0))
+ }
+ }
+ }
+}+
\ No newline at end of file