|
@@ -2,10 +2,15 @@ package main
|
|
|
|
|
|
import (
|
|
import (
|
|
"bytes"
|
|
"bytes"
|
|
|
|
+ "context"
|
|
"fmt"
|
|
"fmt"
|
|
"image"
|
|
"image"
|
|
"io"
|
|
"io"
|
|
"net/http"
|
|
"net/http"
|
|
|
|
+ "os"
|
|
|
|
+ "os/exec"
|
|
|
|
+ "path/filepath"
|
|
|
|
+ "strconv"
|
|
"strings"
|
|
"strings"
|
|
"time"
|
|
"time"
|
|
|
|
|
|
@@ -18,6 +23,7 @@ import (
|
|
"maunium.net/go/mautrix/event"
|
|
"maunium.net/go/mautrix/event"
|
|
"maunium.net/go/mautrix/id"
|
|
"maunium.net/go/mautrix/id"
|
|
"maunium.net/go/mautrix/util"
|
|
"maunium.net/go/mautrix/util"
|
|
|
|
+ "maunium.net/go/mautrix/util/ffmpeg"
|
|
|
|
|
|
"go.mau.fi/mautrix-discord/database"
|
|
"go.mau.fi/mautrix-discord/database"
|
|
)
|
|
)
|
|
@@ -151,6 +157,7 @@ type AttachmentMeta struct {
|
|
MimeType string
|
|
MimeType string
|
|
EmojiName string
|
|
EmojiName string
|
|
CopyIfMissing bool
|
|
CopyIfMissing bool
|
|
|
|
+ Converter func([]byte) ([]byte, string, error)
|
|
}
|
|
}
|
|
|
|
|
|
var NoMeta = AttachmentMeta{}
|
|
var NoMeta = AttachmentMeta{}
|
|
@@ -160,6 +167,78 @@ type attachmentKey struct {
|
|
Encrypt bool
|
|
Encrypt bool
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+func (br *DiscordBridge) convertLottie(data []byte) ([]byte, string, error) {
|
|
|
|
+ fps := br.Config.Bridge.AnimatedSticker.Args.FPS
|
|
|
|
+ width := br.Config.Bridge.AnimatedSticker.Args.Width
|
|
|
|
+ height := br.Config.Bridge.AnimatedSticker.Args.Height
|
|
|
|
+ target := br.Config.Bridge.AnimatedSticker.Target
|
|
|
|
+ var lottieTarget, outputMime string
|
|
|
|
+ switch target {
|
|
|
|
+ case "png":
|
|
|
|
+ lottieTarget = "png"
|
|
|
|
+ outputMime = "image/png"
|
|
|
|
+ fps = 1
|
|
|
|
+ case "gif":
|
|
|
|
+ lottieTarget = "gif"
|
|
|
|
+ outputMime = "image/gif"
|
|
|
|
+ case "webm":
|
|
|
|
+ lottieTarget = "pngs"
|
|
|
|
+ outputMime = "video/webm"
|
|
|
|
+ case "webp":
|
|
|
|
+ lottieTarget = "pngs"
|
|
|
|
+ outputMime = "image/webp"
|
|
|
|
+ case "disable":
|
|
|
|
+ return data, "application/json", nil
|
|
|
|
+ default:
|
|
|
|
+ return nil, "", fmt.Errorf("invalid animated sticker target %q in bridge config", br.Config.Bridge.AnimatedSticker.Target)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ ctx := context.Background()
|
|
|
|
+ tempdir, err := os.MkdirTemp("", "mautrix_discord_lottie_")
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, "", fmt.Errorf("failed to create temp dir: %w", err)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ lottieOutput := filepath.Join(tempdir, "out_")
|
|
|
|
+ if lottieTarget != "pngs" {
|
|
|
|
+ lottieOutput = filepath.Join(tempdir, "output."+lottieTarget)
|
|
|
|
+ }
|
|
|
|
+ cmd := exec.CommandContext(ctx, "lottieconverter", "-", lottieOutput, lottieTarget, fmt.Sprintf("%dx%d", width, height), strconv.Itoa(fps))
|
|
|
|
+ cmd.Stdin = bytes.NewReader(data)
|
|
|
|
+ err = cmd.Run()
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, "", fmt.Errorf("failed to run lottieconverter: %w", err)
|
|
|
|
+ }
|
|
|
|
+ var path string
|
|
|
|
+ if lottieTarget == "pngs" {
|
|
|
|
+ var videoCodec string
|
|
|
|
+ outputExtension := "." + target
|
|
|
|
+ if target == "webm" {
|
|
|
|
+ videoCodec = "libvpx-vp9"
|
|
|
|
+ } else if target == "webp" {
|
|
|
|
+ videoCodec = "libwebp_anim"
|
|
|
|
+ } else {
|
|
|
|
+ panic(fmt.Errorf("impossible case: unknown target %q", target))
|
|
|
|
+ }
|
|
|
|
+ path, err = ffmpeg.ConvertPath(
|
|
|
|
+ ctx, lottieOutput+"*.png", outputExtension,
|
|
|
|
+ []string{"-framerate", strconv.Itoa(fps), "-pattern_type", "glob"},
|
|
|
|
+ []string{"-c:v", videoCodec, "-pix_fmt", "yuva420p", "-f", target},
|
|
|
|
+ false,
|
|
|
|
+ )
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, "", fmt.Errorf("failed to run ffmpeg: %w", err)
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ path = lottieOutput
|
|
|
|
+ }
|
|
|
|
+ data, err = os.ReadFile(path)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, "", fmt.Errorf("failed to read converted file: %w", err)
|
|
|
|
+ }
|
|
|
|
+ return data, outputMime, nil
|
|
|
|
+}
|
|
|
|
+
|
|
func (br *DiscordBridge) copyAttachmentToMatrix(intent *appservice.IntentAPI, url string, encrypt bool, meta AttachmentMeta) (returnDBFile *database.File, returnErr error) {
|
|
func (br *DiscordBridge) copyAttachmentToMatrix(intent *appservice.IntentAPI, url string, encrypt bool, meta AttachmentMeta) (returnDBFile *database.File, returnErr error) {
|
|
isCacheable := !encrypt
|
|
isCacheable := !encrypt
|
|
returnDBFile = br.DB.File.Get(url, encrypt)
|
|
returnDBFile = br.DB.File.Get(url, encrypt)
|
|
@@ -180,6 +259,14 @@ func (br *DiscordBridge) copyAttachmentToMatrix(intent *appservice.IntentAPI, ur
|
|
return
|
|
return
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ if meta.Converter != nil {
|
|
|
|
+ data, meta.MimeType, onceErr = meta.Converter(data)
|
|
|
|
+ if onceErr != nil {
|
|
|
|
+ onceErr = fmt.Errorf("failed to convert attachment: %w", onceErr)
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
onceDBFile, onceErr = br.uploadMatrixAttachment(intent, data, url, encrypt, meta)
|
|
onceDBFile, onceErr = br.uploadMatrixAttachment(intent, data, url, encrypt, meta)
|
|
if onceErr != nil {
|
|
if onceErr != nil {
|
|
return
|
|
return
|