Skip to content

Commit

Permalink
Support Telegram animated stickers (tgs) format
Browse files Browse the repository at this point in the history
This is half a fix for 42wim#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 42wim#1173 or so.)
  • Loading branch information
BenWiederhake committed Jul 17, 2020
1 parent a94fe55 commit 1ec9824
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 2 deletions.
2 changes: 2 additions & 0 deletions bridge/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
43 changes: 42 additions & 1 deletion bridge/helper/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import (
"fmt"
"image/png"
"io"
"io/ioutil"
"net/http"
"os"
"os/exec"
"regexp"
"strings"
"time"
Expand Down Expand Up @@ -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)
Expand All @@ -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
}
28 changes: 27 additions & 1 deletion bridge/telegram/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit 1ec9824

Please sign in to comment.