diff --git a/efb_wechat_slave/__init__.py b/efb_wechat_slave/__init__.py index 37b8cd4..2d88da5 100644 --- a/efb_wechat_slave/__init__.py +++ b/efb_wechat_slave/__init__.py @@ -33,7 +33,7 @@ from .__version__ import __version__ from .chats import ChatManager from .slave_message import SlaveMessageManager -from .utils import ExperimentalFlagsManager +from .utils import ExperimentalFlagsManager, gif_conversion from .vendor import wxpy from .vendor.wxpy import ResponseError from .vendor.wxpy.utils import PuidMap @@ -436,9 +436,6 @@ def send_message(self, msg: Message) -> Message: self.logger.debug( '[%s] Image converted from %s to GIF', msg.uid, msg.mime) file.close() - if f.seek(0, 2) > self.MAX_FILE_SIZE: - raise EFBMessageError( - self._("Image size is too large. (IS02)")) f.seek(0) r.append(self._bot_send_image(chat, f.name, f)) finally: @@ -464,6 +461,8 @@ def send_message(self, msg: Message) -> Message: if not file.closed: file.close() else: + if msg.mime == "image/gif" and file.seek(0, 2) > 1048576: + file = gif_conversion(file) try: if file.seek(0, 2) > self.MAX_FILE_SIZE: raise EFBMessageError( diff --git a/efb_wechat_slave/utils.py b/efb_wechat_slave/utils.py index b6705e3..cb8cfcd 100644 --- a/efb_wechat_slave/utils.py +++ b/efb_wechat_slave/utils.py @@ -2,8 +2,11 @@ import base64 import io import os +import subprocess import json -from typing import Dict, Any, TYPE_CHECKING, List +from tempfile import NamedTemporaryFile +from typing import Dict, Any, TYPE_CHECKING, List, IO + from ehforwarderbot.types import MessageID from .vendor.itchat import utils as itchat_utils @@ -221,3 +224,59 @@ def print_st(): res += base64.b64encode(file.getvalue()).decode() res += print_st() return res + + +if os.name == "nt": + # Workaround for Windows which cannot open the same file as "read" twice. + # Using stdin/stdout pipe for IO with ffmpeg. + # Said to be only working with a few encodings. It seems that Telegram GIF + # (MP4, h264, soundless) luckily felt in that range. + # + # See: https://etm.1a23.studio/issues/90 + + def gif_conversion(file: IO[bytes]) -> IO[bytes]: + """Convert Telegram GIF to real GIF, the NT way.""" + file.seek(0) + new_file_size = os.path.getsize(file.name) + print(f"file_size: {new_file_size/1024}KB") + if new_file_size > 1024 * 1024: + # try to use gifsicle lossy compression + compress_file = NamedTemporaryFile(suffix='.gif') + subprocess.run(["gifsicle", "--resize-method=catrom", "--lossy=100", "-O2", "-o", compress_file.name, file.name], check=True) + new_file_size = os.path.getsize(compress_file.name) + if new_file_size > 1024 * 1024: + scales = [512, 480, 400, 360, 300, 256, 250, 200, 150, 100] + for scale in scales: + subprocess.run(["gifsicle", "--resize-method=catrom", "--resize-fit", f"{scale}x{scale}", "--lossy=100", "-O2", "-o", compress_file.name, file.name], check=True) + new_file_size = os.path.getsize(compress_file.name) + print(f"new_file_size: {new_file_size/1024}KB after resize to {scale}x{scale}") + if new_file_size < 1024 * 1024: + break + file.close() + file = compress_file + file.seek(0) + return file + +else: + def gif_conversion(file: IO[bytes]) -> IO[bytes]: + """Convert Telegram GIF to real GIF, the non-NT way.""" + file.seek(0) + new_file_size = os.path.getsize(file.name) + print(f"file_size: {new_file_size/1024}KB") + if new_file_size > 1024 * 1024: + # try to use gifsicle lossy compression + compress_file = NamedTemporaryFile(suffix='.gif') + subprocess.run(["gifsicle", "--resize-method=catrom", "--lossy=100", "-O2", "-o", compress_file.name, file.name], check=True) + new_file_size = os.path.getsize(compress_file.name) + if new_file_size > 1024 * 1024: + scales = [512, 480, 400, 360, 300, 256, 250, 200, 150, 100] + for scale in scales: + subprocess.run(["gifsicle", "--resize-method=catrom", "--resize-fit", f"{scale}x{scale}", "--lossy=100", "-O2", "-o", compress_file.name, file.name], check=True) + new_file_size = os.path.getsize(compress_file.name) + print(f"new_file_size: {new_file_size/1024}KB after resize to {scale}x{scale}") + if new_file_size < 1024 * 1024: + break + file.close() + file = compress_file + file.seek(0) + return file