Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Re-added save webm and tgs to gif #59

Merged
merged 5 commits into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions TMessagesProj/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ dependencies {
implementation(libs.ktor.client.contentNegotiation)
implementation(libs.ktor.serialization.json)

implementation(files("libs/ffmpeg-kit-video-4.4.LTS.aar"))
implementation(libs.smart.exception.java)
implementation(libs.lottie)

implementation(project(":libs:tcp2ws"))
implementation(project(":libs:pangu"))
ksp(project(":libs:ksp"))
Expand Down
Binary file added TMessagesProj/libs/ffmpeg-kit-video-4.4.LTS.aar
Binary file not shown.
2 changes: 1 addition & 1 deletion TMessagesProj/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,7 @@
<receiver
android:name=".AutoMessageReplyReceiver"
android:exported="false">
e <intent-filter>
<intent-filter>
<action android:name="org.telegram.messenger.ACTION_MESSAGE_REPLY"/>
</intent-filter>
</receiver>
Expand Down
12 changes: 8 additions & 4 deletions TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -29812,6 +29812,11 @@ public void setAutoDeleteHistory(int time, int action) {
options.add(OPTION_ADD_TO_STICKERS_OR_MASKS);
icons.add(R.drawable.msg_sticker);
} else {
// if (!selectedObject.isAnimatedSticker()) {
items.add(LocaleController.getString(R.string.SaveToGallery));
options.add(OPTION_SAVE_STICKER_TO_GALLERY);
icons.add(R.drawable.msg_gallery);
// }
items.add(LocaleController.getString(R.string.AddToStickers));
options.add(OPTION_ADD_TO_STICKERS_OR_MASKS);
icons.add(R.drawable.msg_sticker);
Expand Down Expand Up @@ -29848,12 +29853,11 @@ public void setAutoDeleteHistory(int time, int action) {
icons.add(R.drawable.msg_callback);
}
} else if (type == 9) {
if (!selectedObject.isAnimatedSticker()) {
items.add(LocaleController.getString("SaveToGallery",
R.string.SaveToGallery));
// if (!selectedObject.isAnimatedSticker()) {
items.add(LocaleController.getString(R.string.SaveToGallery));
options.add(OPTION_SAVE_STICKER_TO_GALLERY);
icons.add(R.drawable.msg_gallery);
}
// }
TLRPC.Document document = selectedObject.getDocument();
if (!getMediaDataController().isStickerInFavorites(document)) {
if (getMediaDataController().canAddStickerToFavorites()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import android.content.Context
import android.content.DialogInterface
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.net.Uri
import android.text.TextUtils
import android.util.Base64
Expand All @@ -41,7 +42,14 @@ import android.view.inputmethod.EditorInfo
import android.widget.FrameLayout
import android.widget.TextView
import android.widget.TimePicker
import android.widget.Toast
import androidx.core.content.FileProvider
import com.airbnb.lottie.LottieComposition
import com.airbnb.lottie.LottieCompositionFactory
import com.airbnb.lottie.LottieDrawable
import com.airbnb.lottie.LottieResult
import com.arthenica.ffmpegkit.FFmpegKit
import com.arthenica.ffmpegkit.ReturnCode
import com.google.zxing.EncodeHintType
import com.google.zxing.qrcode.QRCodeWriter
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
Expand Down Expand Up @@ -90,10 +98,12 @@ import org.telegram.ui.Components.EditTextBoldCursor
import org.telegram.ui.Components.Forum.ForumUtilities
import org.telegram.ui.Components.LayoutHelper
import org.telegram.ui.Components.TranscribeButton
import xyz.nextalone.gen.Config
import xyz.nextalone.nnngram.helpers.QrHelper
import xyz.nextalone.nnngram.helpers.QrHelper.readQr
import xyz.nextalone.nnngram.tryOrLog
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.nio.ByteBuffer
import java.nio.charset.StandardCharsets
Expand Down Expand Up @@ -417,7 +427,7 @@ class MessageUtils(num: Int) : BaseController(num) {
}

fun saveStickerToGallery(activity: Activity, messageObject: MessageObject, callback: Utilities.Callback<Uri>) {
saveStickerToGallery(activity, getPathToMessage(messageObject), messageObject.isVideoSticker, callback)
saveStickerToGallery(activity, getPathToMessage(messageObject), messageObject.isVideoSticker, messageObject.isAnimatedSticker, callback)
}

fun addMessageToClipboard(selectedObject: MessageObject, callback: Runnable) {
Expand Down Expand Up @@ -867,14 +877,84 @@ class MessageUtils(num: Int) : BaseController(num) {
if (!temp.exists()) {
return
}
saveStickerToGallery(activity, path, MessageObject.isVideoSticker(document), callback)
saveStickerToGallery(activity, path, MessageObject.isVideoSticker(document), MessageObject.isAnimatedStickerDocument(document), callback)
}

private fun saveStickerToGallery(activity: Activity, path: String?, video: Boolean, callback: Utilities.Callback<Uri>) {
private fun saveStickerToGallery(activity: Activity, path: String?, video: Boolean, animated: Boolean, callback: Utilities.Callback<Uri>) {
Utilities.globalQueue.postRunnable {
tryOrLog {
if (video) {
MediaController.saveFile(path, activity, 1, null, null, callback)
val outputPath =
path!!.replace(".webm", ".gif")
if (File(outputPath).exists()) {
File(outputPath).delete()
}
val cmd = "-y -vcodec libvpx-vp9 -i '$path' -lavfi split[v],palettegen,[v]paletteuse '$outputPath'"
FFmpegKit.executeAsync(cmd) { session ->
val returnCode = session.returnCode
if (ReturnCode.isSuccess(returnCode)) {
MediaController.saveFile(outputPath, activity, 0, null, null, callback)
} else {
Log.e("FFmpegKit", "Failed to convert to GIF: $returnCode, file: $path")
Toast.makeText(activity, "Failed to convert to GIF, Use Mp4", Toast.LENGTH_SHORT).show()
MediaController.saveFile(path, activity, 1, null, null, callback)
}
}
} else if (animated) {
CoroutineScope(Dispatchers.IO).launch {
val outputPath = path!!.replace(".tgs", ".gif")
if (File(outputPath).exists()) {
File(outputPath).delete()
}

val result: LottieResult<LottieComposition> = LottieCompositionFactory.fromJsonInputStreamSync(
FileInputStream(File(path)), path)
val composition: LottieComposition? = result.value

composition?.let { comp ->
val lottieDrawable = LottieDrawable().apply { this.composition = comp }

lottieDrawable.setBounds(0, 0, comp.bounds.width(), comp.bounds.height())

val tempDir = File(activity.cacheDir, "temp_${System.currentTimeMillis()}")
if (!tempDir.exists()) {
tempDir.mkdirs()
}

for (i in comp.startFrame.toInt() until comp.endFrame.toInt()) {
lottieDrawable.frame = i

val bitmap = Bitmap.createBitmap(comp.bounds.width(), comp.bounds.height(), Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
lottieDrawable.draw(canvas)

val file = File(tempDir, "$i.png")
FileOutputStream(file).use { fos ->
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos)
}
}
val generatePaletteCommand = "-i '${tempDir.absolutePath}/%d.png' -vf palettegen=stats_mode=diff -y '${tempDir.absolutePath}/palette.png'"
val createGifCommand = "-framerate 60 -i '${tempDir.absolutePath}/%d.png' -i '${tempDir.absolutePath}/palette.png' -filter_complex [0:v]scale=320:-1:flags=lanczos[v];[v][1:v]paletteuse=dither=none:diff_mode=rectangle -y '$outputPath'"
FFmpegKit.executeAsync(generatePaletteCommand) { session ->
var returnCode = session.returnCode
if (ReturnCode.isSuccess(returnCode)) {
FFmpegKit.executeAsync(createGifCommand) { session1 ->
returnCode = session1.returnCode
if (ReturnCode.isSuccess(returnCode)) {
MediaController.saveFile(outputPath, activity, 0, null, null, callback)
} else {
Log.e("FFmpegKit", "Failed to convert to GIF: $returnCode, file: $path")
Toast.makeText(activity, "Failed to convert to GIF, Use tgs", Toast.LENGTH_SHORT).show()
}
tempDir.deleteRecursively()
}
} else {
Log.e("FFmpegKit", "Failed to convert to GIF: $returnCode, file: $path")
Toast.makeText(activity, "Failed to convert to GIF, Use tgs", Toast.LENGTH_SHORT).show()
}
}
}
}
} else {
val image = BitmapFactory.decodeFile(path)
if (image != null) {
Expand Down
4 changes: 4 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ kotlinxCoroutinesAndroid = "1.9.0"
kotlinxSerializationJson = "1.8.0"
ktor = "3.0.3"
languageId = "17.0.6"
lottie = "6.4.1"
osmdroidAndroid = "6.1.20"
playServicesLocation = "21.3.0"
playServicesVision = "20.1.3"
Expand All @@ -32,6 +33,7 @@ processPhoenix = "3.0.0"
sharetarget = "1.2.0"
paletteKtx = "1.0.0"
rust = "0.9.4"
smartExceptionJava = "0.2.1" # FFmpegKit requires it

[libraries]
checker-compat-qual = { module = "org.checkerframework:checker-compat-qual", version.ref = "checkerCompatQual" }
Expand All @@ -53,6 +55,7 @@ ksp = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref =
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinxCoroutinesAndroid" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
language-id = { module = "com.google.mlkit:language-id", version.ref = "languageId" }
lottie = { module = "com.airbnb.android:lottie", version.ref = "lottie" }
osmdroid-android = { module = "org.osmdroid:osmdroid-android", version.ref = "osmdroidAndroid" }
play-services-location = { module = "com.google.android.gms:play-services-location", version.ref = "playServicesLocation" }
play-services-vision = { module = "com.google.android.gms:play-services-vision", version.ref = "playServicesVision" }
Expand All @@ -70,6 +73,7 @@ ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "kto
ktor-client-encoding = { module = "io.ktor:ktor-client-encoding", version.ref = "ktor" }
ktor-client-contentNegotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
ktor-serialization-json = { module = "io.ktor:ktor-serialization-kotlinx-json-jvm", version.ref = "ktor" }
smart-exception-java = { module = "com.arthenica:smart-exception-java", version.ref = "smartExceptionJava" }

[plugins]
aboutlibraries = { id = "com.mikepenz.aboutlibraries.plugin", version.ref = "aboutlibraries" }
Expand Down
Loading