From 53cafa9f3d0c8be33821fc7338b1da97e91d9cc6 Mon Sep 17 00:00:00 2001
From: Benau <Benau@users.noreply.github.com>
Date: Wed, 25 Aug 2021 04:32:50 +0800
Subject: Convert .tgs with go libraries (and cgo) (telegram) (#1569)

This commit adds support for go/cgo tgs conversion when building with the -tags `cgo`
The default binaries are still "pure" go and uses the old way of converting.

* Move lottie_convert.py conversion code to its own file

* Add optional libtgsconverter

* Update vendor

* Apply suggestions from code review

* Update bridge/helper/libtgsconverter.go

Co-authored-by: Wim <wim@42.be>
---
 bridge/helper/helper.go          | 66 -----------------------------
 bridge/helper/libtgsconverter.go | 34 +++++++++++++++
 bridge/helper/lottie_convert.go  | 89 ++++++++++++++++++++++++++++++++++++++++
 bridge/telegram/handlers.go      | 18 ++------
 bridge/telegram/telegram.go      |  8 ++--
 5 files changed, 130 insertions(+), 85 deletions(-)
 create mode 100644 bridge/helper/libtgsconverter.go
 create mode 100644 bridge/helper/lottie_convert.go

(limited to 'bridge')

diff --git a/bridge/helper/helper.go b/bridge/helper/helper.go
index 1bdd8a40..581a2c43 100644
--- a/bridge/helper/helper.go
+++ b/bridge/helper/helper.go
@@ -5,10 +5,7 @@ import (
 	"fmt"
 	"image/png"
 	"io"
-	"io/ioutil"
 	"net/http"
-	"os"
-	"os/exec"
 	"regexp"
 	"strings"
 	"time"
@@ -239,66 +236,3 @@ func ConvertWebPToPNG(data *[]byte) error {
 	*data = w.Bytes()
 	return nil
 }
-
-// CanConvertTgsToX Checks whether the external command necessary for ConvertTgsToX works.
-func CanConvertTgsToX() error {
-	// We depend on the fact that `lottie_convert.py --help` has exit status 0.
-	// Hyrum's Law predicted this, and Murphy's Law predicts that this will break eventually.
-	// However, there is no alternative like `lottie_convert.py --is-properly-installed`
-	cmd := exec.Command("lottie_convert.py", "--help")
-	return cmd.Run()
-}
-
-// 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, outputFormat string, logger *logrus.Entry) error {
-	// lottie can't handle input from a pipe, so write to a temporary file:
-	tmpInFile, err := ioutil.TempFile(os.TempDir(), "matterbridge-lottie-input-*.tgs")
-	if err != nil {
-		return err
-	}
-	tmpInFileName := tmpInFile.Name()
-	defer func() {
-		if removeErr := os.Remove(tmpInFileName); removeErr != nil {
-			logger.Errorf("Could not delete temporary (input) file %s: %v", tmpInFileName, removeErr)
-		}
-	}()
-	// lottie can handle writing to a pipe, but there is no way to do that platform-independently.
-	// "/dev/stdout" won't work on Windows, and "-" upsets Cairo for some reason. So we need another file:
-	tmpOutFile, err := ioutil.TempFile(os.TempDir(), "matterbridge-lottie-output-*.data")
-	if err != nil {
-		return err
-	}
-	tmpOutFileName := tmpOutFile.Name()
-	defer func() {
-		if removeErr := os.Remove(tmpOutFileName); removeErr != nil {
-			logger.Errorf("Could not delete temporary (output) file %s: %v", tmpOutFileName, removeErr)
-		}
-	}()
-
-	if _, writeErr := tmpInFile.Write(*data); writeErr != nil {
-		return writeErr
-	}
-	// Must close before calling lottie to avoid data races:
-	if closeErr := tmpInFile.Close(); closeErr != nil {
-		return closeErr
-	}
-
-	// Call lottie to transform:
-	cmd := exec.Command("lottie_convert.py", "--input-format", "lottie", "--output-format", outputFormat, tmpInFileName, tmpOutFileName)
-	cmd.Stdout = nil
-	cmd.Stderr = nil
-	// NB: lottie writes progress into to stderr in all cases.
-	_, stderr := cmd.Output()
-	if stderr != nil {
-		// 'stderr' already contains some parts of Stderr, because it was set to 'nil'.
-		return stderr
-	}
-	dataContents, err := ioutil.ReadFile(tmpOutFileName)
-	if err != nil {
-		return err
-	}
-
-	*data = dataContents
-	return nil
-}
diff --git a/bridge/helper/libtgsconverter.go b/bridge/helper/libtgsconverter.go
new file mode 100644
index 00000000..7181da90
--- /dev/null
+++ b/bridge/helper/libtgsconverter.go
@@ -0,0 +1,34 @@
+// +build cgo
+
+package helper
+
+import (
+	"fmt"
+	"github.com/Benau/tgsconverter/libtgsconverter"
+	"github.com/sirupsen/logrus"
+)
+
+func CanConvertTgsToX() error {
+	return nil
+}
+
+// ConvertTgsToX convert input data (which should be tgs format) to any format supported by libtgsconverter
+func ConvertTgsToX(data *[]byte, outputFormat string, logger *logrus.Entry) error {
+	options := libtgsconverter.NewConverterOptions()
+	options.SetExtension(outputFormat)
+	blob, err := libtgsconverter.ImportFromData(*data, options)
+	if err != nil {
+		return fmt.Errorf("failed to run libtgsconverter.ImportFromData: %s", err.Error())
+	}
+
+	*data = blob
+	return nil
+}
+
+func SupportsFormat(format string) bool {
+	return libtgsconverter.SupportsExtension(format)
+}
+
+func LottieBackend() string {
+	return "libtgsconverter"
+}
diff --git a/bridge/helper/lottie_convert.go b/bridge/helper/lottie_convert.go
new file mode 100644
index 00000000..c6ae0a7a
--- /dev/null
+++ b/bridge/helper/lottie_convert.go
@@ -0,0 +1,89 @@
+// +build !cgo
+
+package helper
+
+import (
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"github.com/sirupsen/logrus"
+)
+
+// CanConvertTgsToX Checks whether the external command necessary for ConvertTgsToX works.
+func CanConvertTgsToX() error {
+	// We depend on the fact that `lottie_convert.py --help` has exit status 0.
+	// Hyrum's Law predicted this, and Murphy's Law predicts that this will break eventually.
+	// However, there is no alternative like `lottie_convert.py --is-properly-installed`
+	cmd := exec.Command("lottie_convert.py", "--help")
+	return cmd.Run()
+}
+
+// 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, outputFormat string, logger *logrus.Entry) error {
+	// lottie can't handle input from a pipe, so write to a temporary file:
+	tmpInFile, err := ioutil.TempFile(os.TempDir(), "matterbridge-lottie-input-*.tgs")
+	if err != nil {
+		return err
+	}
+	tmpInFileName := tmpInFile.Name()
+	defer func() {
+		if removeErr := os.Remove(tmpInFileName); removeErr != nil {
+			logger.Errorf("Could not delete temporary (input) file %s: %v", tmpInFileName, removeErr)
+		}
+	}()
+	// lottie can handle writing to a pipe, but there is no way to do that platform-independently.
+	// "/dev/stdout" won't work on Windows, and "-" upsets Cairo for some reason. So we need another file:
+	tmpOutFile, err := ioutil.TempFile(os.TempDir(), "matterbridge-lottie-output-*.data")
+	if err != nil {
+		return err
+	}
+	tmpOutFileName := tmpOutFile.Name()
+	defer func() {
+		if removeErr := os.Remove(tmpOutFileName); removeErr != nil {
+			logger.Errorf("Could not delete temporary (output) file %s: %v", tmpOutFileName, removeErr)
+		}
+	}()
+
+	if _, writeErr := tmpInFile.Write(*data); writeErr != nil {
+		return writeErr
+	}
+	// Must close before calling lottie to avoid data races:
+	if closeErr := tmpInFile.Close(); closeErr != nil {
+		return closeErr
+	}
+
+	// Call lottie to transform:
+	cmd := exec.Command("lottie_convert.py", "--input-format", "lottie", "--output-format", outputFormat, tmpInFileName, tmpOutFileName)
+	cmd.Stdout = nil
+	cmd.Stderr = nil
+	// NB: lottie writes progress into to stderr in all cases.
+	_, stderr := cmd.Output()
+	if stderr != nil {
+		// 'stderr' already contains some parts of Stderr, because it was set to 'nil'.
+		return stderr
+	}
+	dataContents, err := ioutil.ReadFile(tmpOutFileName)
+	if err != nil {
+		return err
+	}
+
+	*data = dataContents
+	return nil
+}
+
+func SupportsFormat(format string) bool {
+	switch format {
+	case "png":
+		fallthrough
+	case "webp":
+		return true
+	default:
+		return false
+	}
+	return false
+}
+
+func LottieBackend() string {
+	return "lottie_convert.py"
+}
diff --git a/bridge/telegram/handlers.go b/bridge/telegram/handlers.go
index a93c71bb..84881f7f 100644
--- a/bridge/telegram/handlers.go
+++ b/bridge/telegram/handlers.go
@@ -220,20 +220,10 @@ func (b *Btelegram) handleDownloadAvatar(userid int, channel string) {
 }
 
 func (b *Btelegram) maybeConvertTgs(name *string, data *[]byte) {
-	var format string
-	switch b.GetString("MediaConvertTgs") {
-	case FormatWebp:
-		b.Log.Debugf("Tgs to WebP conversion enabled, converting %v", name)
-		format = FormatWebp
-	case FormatPng:
-		// 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 %v", name)
-		format = FormatPng
-	default:
+	format := b.GetString("MediaConvertTgs")
+	if helper.SupportsFormat(format) {
+		b.Log.Debugf("Format supported by %s, converting %v", helper.LottieBackend(), name)
+	} else {
 		// 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.
diff --git a/bridge/telegram/telegram.go b/bridge/telegram/telegram.go
index 0f08a45b..199a76ab 100644
--- a/bridge/telegram/telegram.go
+++ b/bridge/telegram/telegram.go
@@ -17,8 +17,6 @@ const (
 	HTMLFormat  = "HTML"
 	HTMLNick    = "htmlnick"
 	MarkdownV2  = "MarkdownV2"
-	FormatPng   = "png"
-	FormatWebp  = "webp"
 )
 
 type Btelegram struct {
@@ -32,10 +30,10 @@ func New(cfg *bridge.Config) bridge.Bridger {
 	if tgsConvertFormat != "" {
 		err := helper.CanConvertTgsToX()
 		if err != nil {
-			log.Fatalf("Telegram bridge configured to convert .tgs files to '%s', but lottie does not appear to work:\n%#v", tgsConvertFormat, err)
+			log.Fatalf("Telegram bridge configured to convert .tgs files to '%s', but %s does not appear to work:\n%#v", tgsConvertFormat, helper.LottieBackend(), err)
 		}
-		if tgsConvertFormat != FormatPng && tgsConvertFormat != FormatWebp {
-			log.Fatalf("Telegram bridge configured to convert .tgs files to '%s', but only '%s' and '%s' are supported.", FormatPng, FormatWebp, tgsConvertFormat)
+		if !helper.SupportsFormat(tgsConvertFormat) {
+			log.Fatalf("Telegram bridge configured to convert .tgs files to '%s', but %s doesn't support it.", tgsConvertFormat, helper.LottieBackend())
 		}
 	}
 	return &Btelegram{Config: cfg, avatarMap: make(map[string]string)}
-- 
cgit v1.2.3