Skip to content

Commit

Permalink
feat: save webm and tgs to gif
Browse files Browse the repository at this point in the history
Signed-off-by: Next Alone <[email protected]>
  • Loading branch information
NextAlone committed Feb 16, 2024
1 parent 65e6931 commit 54c3e29
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 8 deletions.
3 changes: 3 additions & 0 deletions TMessagesProj/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ dependencies {
implementation(libs.ktor.client.contentNegotiation)
implementation(libs.ktor.serialization.json)

implementation(libs.ffmpeg)
implementation(libs.lottie)

implementation(project(":libs:tcp2ws"))
implementation(project(":libs:pangu"))
ksp(project(":libs:ksp"))
Expand Down
8 changes: 4 additions & 4 deletions TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -26973,12 +26973,12 @@ 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()) {
// if (!selectedObject.isAnimatedSticker()) {
items.add(LocaleController.getString("SaveToGallery",
R.string.SaveToGallery));
options.add(OPTION_SAVE_STICKER_TO_GALLERY);
icons.add(R.drawable.msg_gallery);
}
// }
items.add(LocaleController.getString("AddToStickers",
R.string.AddToStickers));
options.add(OPTION_ADD_TO_STICKERS_OR_MASKS);
Expand Down Expand Up @@ -27018,12 +27018,12 @@ public void setAutoDeleteHistory(int time, int action) {
icons.add(R.drawable.msg_callback);
}
} else if (type == 9) {
if (!selectedObject.isAnimatedSticker()) {
// if (!selectedObject.isAnimatedSticker()) {
items.add(LocaleController.getString("SaveToGallery",
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 (MessageObject.isStickerHasSet(document)) {
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.mobileffmpeg.Config.RETURN_CODE_SUCCESS
import com.arthenica.mobileffmpeg.FFmpeg
import com.google.zxing.EncodeHintType
import com.google.zxing.qrcode.QRCodeWriter
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
Expand Down Expand Up @@ -93,6 +101,7 @@ import org.telegram.ui.Components.TranscribeButton
import xyz.nextalone.nnngram.helpers.QrHelper
import xyz.nextalone.nnngram.helpers.QrHelper.readQr
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 @@ -416,7 +425,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 @@ -866,14 +875,94 @@ 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 = arrayOf("-y", "-i", path, "-vf", "colorkey=0x000000:0.1:0.1,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse", outputPath)
FFmpeg.executeAsync(cmd) { executionId, returnCode ->
if (returnCode == RETURN_CODE_SUCCESS) {
MediaController.saveFile(outputPath, activity, 0, null, null, callback)
} else {
Log.e("FFmpeg", "Failed to convert to GIF: $returnCode, file: $path")
Toast.makeText(activity, "Failed to convert to GIF, Use Mp4", Toast.LENGTH_SHORT).show()
com.arthenica.mobileffmpeg.Config.printLastCommandOutput(android.util.Log.ERROR)
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 = arrayOf(
"-i", "${tempDir.absolutePath}/%d.png",
"-vf", "palettegen=stats_mode=diff",
"-y", "${tempDir.absolutePath}/palette.png"
)
val createGifCommand = arrayOf(
"-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
)
FFmpeg.executeAsync(generatePaletteCommand) { executionId, returnCode ->
if (returnCode == RETURN_CODE_SUCCESS) {
FFmpeg.executeAsync(createGifCommand) { executionId, returnCode ->
if (returnCode == RETURN_CODE_SUCCESS) {
MediaController.saveFile(outputPath, activity, 0, null, null, callback)
} else {
Log.e("FFmpeg", "Failed to convert to GIF: $returnCode, file: $path")
Toast.makeText(activity, "Failed to convert to GIF, Use tgs", Toast.LENGTH_SHORT).show()
com.arthenica.mobileffmpeg.Config.printLastCommandOutput(android.util.Log.ERROR)
}
tempDir.deleteRecursively()
}
} else {
Log.e("FFmpeg", "Failed to convert to GIF: $returnCode, file: $path")
Toast.makeText(activity, "Failed to convert to GIF, Use tgs", Toast.LENGTH_SHORT).show()
com.arthenica.mobileffmpeg.Config.printLastCommandOutput(android.util.Log.ERROR)
}
}
}
}
} else {
val image = BitmapFactory.decodeFile(path)
if (image != null) {
Expand Down
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ 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" }
ffmpeg = { module = "com.arthenica:mobile-ffmpeg-min", version = "4.4.LTS" }
lottie = { module = "com.airbnb.android:lottie", version = "4.1.0" }

[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
Expand Down

0 comments on commit 54c3e29

Please sign in to comment.