From 1ec982495c787a8fecd6a224da155396f506acc5 Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Fri, 17 Jul 2020 21:51:20 +0200 Subject: [PATCH] Support Telegram animated stickers (tgs) format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is half a fix for #874 This patch introduces two new config flags: - MediaConvertTgsToWebP - MediaConvertTgsToPNG These need to be treated independently from the existing MediaConvertWebPToPNG flag because Tgs→WebP results in an *animated* WebP, and the WebP→PNG converter can't handle animated WebP files yet. Furthermore, some platforms (like discord) don't even support animated WebP files, so the user may want to fall back to static PNGs (not APNGs). The final reason why this is only half a fix is that this introduces an external dependency, namely lottie, to be installed like this: $ pip3 install lottie cairosvg This patch works by writing the tgs to a temporary file in /tmp, calling lottie to convert it (this conversion may take several seconds!), and then deleting the temporary file. The temporary file is absolutely necessary, as lottie refuses to work on non-seekable files. If anyone comes up with a reasonable use case where /tmp is unavailable, I can add yet another config option for that, if desired. I will propose new text for the Wiki in the PR for this patch. (Should be #1173 or so.) --- bridge/config/config.go | 2 ++ bridge/helper/helper.go | 43 ++++++++++++++++++++++++++++++++++++- bridge/telegram/handlers.go | 28 +++++++++++++++++++++++- 3 files changed, 71 insertions(+), 2 deletions(-) diff --git a/bridge/config/config.go b/bridge/config/config.go index d98c942362..9e0370371f 100644 --- a/bridge/config/config.go +++ b/bridge/config/config.go @@ -98,6 +98,8 @@ type Protocol struct { MediaDownloadSize int // all protocols MediaServerDownload string MediaServerUpload string + MediaConvertTgsToPNG bool // telegram + MediaConvertTgsToWebP bool // telegram MediaConvertWebPToPNG bool // telegram MessageDelay int // IRC, time in millisecond to wait between messages MessageFormat string // telegram diff --git a/bridge/helper/helper.go b/bridge/helper/helper.go index 412447661a..771dd7b992 100644 --- a/bridge/helper/helper.go +++ b/bridge/helper/helper.go @@ -5,7 +5,10 @@ import ( "fmt" "image/png" "io" + "io/ioutil" "net/http" + "os" + "os/exec" "regexp" "strings" "time" @@ -192,7 +195,7 @@ func ParseMarkdown(input string) string { return res } -// ConvertWebPToPNG convert input data (which should be WebP format to PNG format) +// ConvertWebPToPNG converts input data (which should be WebP format) to PNG format func ConvertWebPToPNG(data *[]byte) error { r := bytes.NewReader(*data) m, err := webp.Decode(r) @@ -207,3 +210,41 @@ func ConvertWebPToPNG(data *[]byte) error { *data = w.Bytes() return nil } + +func maybeCleanup(name string, logger *logrus.Entry) { + if err := os.Remove(name); err != nil { + logger.Errorf("Could not delete temporary file %s: %v", name, err) + } +} + +// ConvertTgsToWebP convert input data (which should be tgs format) to WebP format +// This relies on an external command, which is ugly, but works. +func ConvertTgsToX(data *[]byte, output_format string, logger *logrus.Entry) error { + // lottie can't handle input from a pipe, so write to a temporary file: + tmpFile, err := ioutil.TempFile(os.TempDir(), "matterbridge-lottie-*.tgs") + if err != nil { + return err + } + tmpFileName := tmpFile.Name() + defer maybeCleanup(tmpFileName, logger) + if _, err := tmpFile.Write(*data); err != nil { + return err + } + // Must close before calling lottie to avoid data races: + if err := tmpFile.Close(); err != nil { + return err + } + + // Call lottie to transform: + cmd := exec.Command("lottie_convert.py", "--input-format", "lottie", "--output-format", output_format, tmpFileName, "/dev/stdout") + cmd.Stderr = nil + // NB: lottie writes progress into to stderr in all cases. + out, err := cmd.Output() + if err != nil { + // 'err' already contains some parts of Stderr, because it was set to 'nil'. + return err + } + + *data = out + return nil +} diff --git a/bridge/telegram/handlers.go b/bridge/telegram/handlers.go index 5c60f74be9..dd0f8f3d13 100644 --- a/bridge/telegram/handlers.go +++ b/bridge/telegram/handlers.go @@ -264,7 +264,33 @@ func (b *Btelegram) handleDownload(rmsg *config.Message, message *tgbotapi.Messa if err != nil { return err } - if strings.HasSuffix(name, ".webp") && b.GetBool("MediaConvertWebPToPNG") { + if strings.HasSuffix(name, ".tgs.webp") { + if b.GetBool("MediaConvertTgsToWebP") { + b.Log.Debugf("Tgs to WebP conversion enabled, converting %s", name) + err := helper.ConvertTgsToX(data, "webp", b.Log) + if err != nil { + b.Log.Errorf("conversion failed: %s", err) + } else { + name = strings.Replace(name, ".tgs.webp", ".webp", 1) + } + } else if b.GetBool("MediaConvertTgsToPNG") { + // The WebP to PNG converter can't handle animated webp files yet, + // and I'm not going to write a path for x/image/webp. + // The error message would be: + // conversion failed: webp: non-Alpha VP8X is not implemented + // So instead, we tell lottie to directly go to PNG: + b.Log.Debugf("Tgs to PNG conversion enabled, converting %s", name) + err := helper.ConvertTgsToX(data, "png", b.Log) + if err != nil { + b.Log.Errorf("conversion failed: %s", err) + } else { + name = strings.Replace(name, ".tgs.webp", ".png", 1) + } + } + // Otherwise, no conversion was requested. Trying to run the usual webp + // converter would fail, because '.tgs.webp' is actually a gzipped JSON + // file, and has nothing to do with WebP. + } else if strings.HasSuffix(name, ".webp") && b.GetBool("MediaConvertWebPToPNG") { b.Log.Debugf("WebP to PNG conversion enabled, converting %s", name) err := helper.ConvertWebPToPNG(data) if err != nil {