From c54ae9548f573d6ca10bde464fe823171c5c9522 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sun, 14 Aug 2022 00:46:02 +0300 Subject: [PATCH] Add support for converting video stickers to images --- CHANGELOG.md | 5 +- mautrix_telegram/config.py | 5 ++ mautrix_telegram/example-config.yaml | 11 ++++ .../portal_util/message_convert.py | 4 +- mautrix_telegram/util/file_transfer.py | 17 ++++-- mautrix_telegram/util/tgs_converter.py | 2 +- mautrix_telegram/util/webm_converter.py | 52 +++++++++++++++++++ 7 files changed, 89 insertions(+), 7 deletions(-) create mode 100644 mautrix_telegram/util/webm_converter.py diff --git a/CHANGELOG.md b/CHANGELOG.md index aa95f159..9948c090 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ Minimum Conduit version remains at 0.4.0. ### Added * Added provisioning API for resolving Telegram identifiers (like usernames). -* Added basic bridging of Telegram custom emojis to Matrix. +* Added support for bridging Telegram custom emojis to Matrix. * Added option to not bridge chats with lots of members. * Added option to include captions in the same message as the media to implement [MSC2530]. Sending captions the same way is also supported and @@ -33,6 +33,9 @@ Minimum Conduit version remains at 0.4.0. ### Improved * Improved handling the bridge user leaving chats on Telegram, and new users being added on Telegram. +* Improved animated sticker conversion options: added support for animated webp + and added option to convert video stickers (webm) to the specified image + format. * Audio and video metadata is now bridged properly to Telegram. * Added database index on Telegram usernames (used when bridging username @-mentions in messages). diff --git a/mautrix_telegram/config.py b/mautrix_telegram/config.py index 861569af..d974a9e1 100644 --- a/mautrix_telegram/config.py +++ b/mautrix_telegram/config.py @@ -145,9 +145,14 @@ class Config(BaseBridgeConfig): copy("bridge.parallel_file_transfer") copy("bridge.federate_rooms") copy("bridge.animated_sticker.target") + copy("bridge.animated_sticker.convert_from_webm") copy("bridge.animated_sticker.args.width") copy("bridge.animated_sticker.args.height") copy("bridge.animated_sticker.args.fps") + copy("bridge.animated_emoji.target") + copy("bridge.animated_emoji.args.width") + copy("bridge.animated_emoji.args.height") + copy("bridge.animated_emoji.args.fps") copy("bridge.private_chat_portal_meta") copy("bridge.delivery_receipts") copy("bridge.delivery_error_reports") diff --git a/mautrix_telegram/example-config.yaml b/mautrix_telegram/example-config.yaml index b6f1c291..ad0366d9 100644 --- a/mautrix_telegram/example-config.yaml +++ b/mautrix_telegram/example-config.yaml @@ -230,11 +230,22 @@ bridge: # webm - converts to webm video, requires ffmpeg executable with vp9 codec and webm container support # webp - converts to animated webp, requires ffmpeg executable with webp codec/container support target: gif + # Should video stickers be converted to the specified format as well? + convert_from_webm: false # Arguments for converter. All converters take width and height. args: width: 256 height: 256 fps: 25 # only for webm, webp and gif (2, 5, 10, 20 or 25 recommended) + # Settings for converting animated emoji. + # Same as animated_sticker, but webm is not supported as the target + # (because inline images can only contain images, not videos). + animated_emoji: + target: webp + args: + width: 64 + height: 64 + fps: 25 # End-to-bridge encryption support options. # # See https://docs.mau.fi/bridges/general/end-to-bridge-encryption.html for more info. diff --git a/mautrix_telegram/portal_util/message_convert.py b/mautrix_telegram/portal_util/message_convert.py index 23a1c238..072c0c1b 100644 --- a/mautrix_telegram/portal_util/message_convert.py +++ b/mautrix_telegram/portal_util/message_convert.py @@ -413,13 +413,15 @@ class TelegramMessageConverter: thumb_loc = None thumb_size = None parallel_id = source.tgid if self.config["bridge.parallel_file_transfer"] else None + tgs_convert = self.config["bridge.animated_sticker"] file = await util.transfer_file_to_matrix( source.client, intent, document, thumb_loc, is_sticker=attrs.is_sticker, - tgs_convert=self.config["bridge.animated_sticker"], + tgs_convert=tgs_convert, + webm_convert=tgs_convert["target"] if tgs_convert["convert_from_webm"] else None, filename=attrs.name, parallel_id=parallel_id, encrypt=self.portal.encrypted, diff --git a/mautrix_telegram/util/file_transfer.py b/mautrix_telegram/util/file_transfer.py index aab5ffe9..aa7d0d01 100644 --- a/mautrix_telegram/util/file_transfer.py +++ b/mautrix_telegram/util/file_transfer.py @@ -52,6 +52,7 @@ from ..tgclient import MautrixTelegramClient from ..util import sane_mimetypes from .parallel_file_transfer import parallel_transfer_to_matrix from .tgs_converter import convert_tgs_to +from .webm_converter import convert_webm_to try: from PIL import Image @@ -228,10 +229,8 @@ async def transfer_custom_emojis_to_matrix( GetCustomEmojiDocumentsRequest(document_id=not_existing_ids) ) - tgs_args = source.config["bridge.animated_sticker"] - if tgs_args["target"] == "webm": - # Inline images can't be videos, let's hope animated webp is supported - tgs_args = {**tgs_args, "target": "webp"} + tgs_args = source.config["bridge.animated_emoji"] + webm_convert = tgs_args["target"] transfer_sema = asyncio.Semaphore(5) @@ -243,6 +242,7 @@ async def transfer_custom_emojis_to_matrix( document, is_sticker=True, tgs_convert=tgs_args, + webm_convert=webm_convert, filename=f"emoji-{document.id}", # Emojis are used as inline images and can't be encrypted encrypt=False, @@ -261,6 +261,7 @@ async def transfer_file_to_matrix( *, is_sticker: bool = False, tgs_convert: dict | None = None, + webm_convert: str | None = None, filename: str | None = None, encrypt: bool = False, parallel_id: int | None = None, @@ -290,6 +291,7 @@ async def transfer_file_to_matrix( thumbnail, is_sticker, tgs_convert, + webm_convert, filename, encrypt, parallel_id, @@ -305,6 +307,7 @@ async def _unlocked_transfer_file_to_matrix( thumbnail: TypeThumbnail, is_sticker: bool, tgs_convert: dict | None, + webm_convert: str | None, filename: str | None, encrypt: bool, parallel_id: int | None, @@ -348,6 +351,12 @@ async def _unlocked_transfer_file_to_matrix( width, height = converted_anim.width, converted_anim.height image_converted = mime_type != "application/gzip" thumbnail = None + elif is_sticker and webm_convert and webm_convert != "webm" and mime_type == "video/webm": + converted_anim = await convert_webm_to(file, webm_convert) + mime_type = converted_anim.mime + file = converted_anim.data + image_converted = mime_type != "video/webm" + thumbnail = None decryption_info = None upload_mime_type = mime_type diff --git a/mautrix_telegram/util/tgs_converter.py b/mautrix_telegram/util/tgs_converter.py index dc48c508..fa8caad7 100644 --- a/mautrix_telegram/util/tgs_converter.py +++ b/mautrix_telegram/util/tgs_converter.py @@ -99,7 +99,7 @@ if lottieconverter: converters["png"] = tgs_to_png converters["gif"] = tgs_to_gif -if lottieconverter and ffmpeg: +if lottieconverter and ffmpeg.ffmpeg_path: async def tgs_to_webm( file: bytes, width: int, height: int, fps: int = 30, **_: Any diff --git a/mautrix_telegram/util/webm_converter.py b/mautrix_telegram/util/webm_converter.py new file mode 100644 index 00000000..6ba4eed0 --- /dev/null +++ b/mautrix_telegram/util/webm_converter.py @@ -0,0 +1,52 @@ +# mautrix-telegram - A Matrix-Telegram puppeting bridge +# Copyright (C) 2022 Tulir Asokan +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +from __future__ import annotations + +import logging + +from mautrix.util import ffmpeg + +from .tgs_converter import ConvertedSticker + +log: logging.Logger = logging.getLogger("mau.util.webm") + + +converter_args = { + "gif": { + "output_args": ("-vf", "split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse"), + }, + "png": { + "input_args": ("-ss", "0"), + "output_args": ("-frames:v", "1"), + }, + "webp": {}, +} + + +async def convert_webm_to(file: bytes, convert_to: str) -> ConvertedSticker: + if convert_to in ("png", "gif", "webp"): + try: + converted_data = await ffmpeg.convert_bytes( + data=file, + output_extension=f".{convert_to}", + **converter_args[convert_to], + ) + return ConvertedSticker(f"image/{convert_to}", converted_data) + except ffmpeg.ConverterError as e: + log.error(str(e)) + elif convert_to != "disable": + log.warning(f"Unable to convert webm animated sticker, type {convert_to} not supported") + return ConvertedSticker("video/webm", file)