diff --git a/efb_telegram_master/utils.py b/efb_telegram_master/utils.py index f6b9cc77..d3c8d6a9 100644 --- a/efb_telegram_master/utils.py +++ b/efb_telegram_master/utils.py @@ -160,6 +160,22 @@ def chat_id_str_to_id(s: EFBChannelChatIDStr) -> Tuple[ModuleID, ChatID, Optiona group_id = ChatID(ids[2]) return channel_id, chat_uid, group_id +def _png_gif_prepare(image): + """ Fork of lottie.exporters.gif.export_gif + Adapted from eltiempoes/python-lottie + https://github.com/eltiempoes/python-lottie/blob/a9f8be4858adb7eb0bc0e406a870b19c309c8a36/lib/lottie/exporters/gif.py#L10 + License: + AGPL 3.0 (Python Lottie) + """ + if image.mode not in ["RGBA", "RGBa"]: + image = image.convert("RGBA") + alpha = image.getchannel("A") + image = image.convert(image.mode[:-1]) \ + .convert('P', palette=Image.ADAPTIVE, colors=255) # changed + mask = Image.eval(alpha, lambda a: 255 if a <= 128 else 0) + image.paste(255, mask=mask) + image.info['transparency'] = 255 # added + return image def export_gif(animation, fp, dpi=96, skip_frames=5): """ Fork of lottie.exporters.gif.export_gif @@ -172,7 +188,7 @@ def export_gif(animation, fp, dpi=96, skip_frames=5): # Import only upon calling the method due to added binary dependencies # (libcairo) from lottie.exporters.cairo import export_png - from lottie.exporters.gif import _png_gif_prepare + # from lottie.exporters.gif import _png_gif_prepare # The code here have some problem, so I copy the function abo ve start = int(animation.in_point) end = int(animation.out_point) @@ -258,12 +274,35 @@ def gif_conversion(file: IO[bytes], channel_id: str) -> IO[bytes]: # Set input/output of ffmpeg to stream stream = ffmpeg.input("pipe:") - if channel_id.startswith("blueset.wechat") and metadata.get('width', 0) > 600: + if metadata['streams'][0]['codec_name'] == 'vp9': + stream = ffmpeg.input(file.name, vcodec='libvpx-vp9') + if channel_id.startswith("blueset.wechat"): # Workaround: Compress GIF for slave channel `blueset.wechat` # TODO: Move this logic to `blueset.wechat` in the future - stream = stream.filter("scale", 600, -2) + if metadata.get('width', 0) > 600: + stream = stream.filter("scale", 600, -2) + if metadata.get('fps', 0) > 12: + stream = stream.filter("fps", 12, round='up') + split = ( + stream + .split() + ) + stream_paletteuse = ( + ffmpeg + .filter( + [ + split[0], + split[1] + .filter( + filter_name='palettegen', + reserve_transparent='on', + ) + ], + filter_name='paletteuse', + ) + ) # Need to specify file format here as no extension hint presents. - args = stream.output("pipe:", format="gif").compile() + args = stream_paletteuse.output("pipe:", format="gif").compile() file.seek(0) # subprocess.Popen would still try to access the file handle instead of @@ -294,11 +333,54 @@ def gif_conversion(file: IO[bytes], channel_id: str) -> IO[bytes]: file.seek(0) metadata = ffmpeg.probe(file.name) stream = ffmpeg.input(file.name) - if channel_id.startswith("blueset.wechat") and metadata.get('width', 0) > 600: + # 检查视频编码类型是否为VP9 + if metadata['streams'][0]['codec_name'] == 'vp9': + stream = ffmpeg.input(file.name, vcodec='libvpx-vp9') # 只有这个能保持透明背景 + if channel_id.startswith("blueset.wechat"): # Workaround: Compress GIF for slave channel `blueset.wechat` # TODO: Move this logic to `blueset.wechat` in the future - stream = stream.filter("scale", 600, -2) - stream.output(gif_file.name).overwrite_output().run() + if metadata.get('fps', 0) > 12: + stream = stream.filter("fps", 12, round='up') # 限制帧率 + if metadata.get('width', 0) > 600: + stream = stream.filter("scale", 600, -2) # 限制宽度 + split = ( + stream + .split() + ) + stream_paletteuse = ( + ffmpeg + .filter( + [ + split[0], + split[1] + .filter( + filter_name='palettegen', + reserve_transparent='on', + ) + ], + filter_name='paletteuse', + ) + ) + stream_paletteuse.output(gif_file.name).overwrite_output().run() + new_file_size = os.path.getsize(gif_file.name) + print(f"file_size: {new_file_size/1024}KB") + if new_file_size > 1024 * 1024 and channel_id.startswith("blueset.wechat"): + # try to use gifsicle lossy compression + compress_file = NamedTemporaryFile(suffix='.gif') + subprocess.run(["gifsicle", "--resize-method=catrom", "--lossy=100", "-O2", "-o", compress_file.name, gif_file.name], check=True) + new_file_size = os.path.getsize(compress_file.name) + if new_file_size > 1024 * 1024: + scales = [600, 512, 480, 400, 360, 300, 256, 250, 200, 150, 100] + scales = [scale for scale in scales if scale < metadata['streams'][0]['width']] + scales = sorted(scales, reverse=True) + for scale in scales: + subprocess.run(["gifsicle", "--resize-method=catrom", "--resize-fit", f"{scale}x{scale}", "--lossy=100", "-O2", "-o", compress_file.name, gif_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 + gif_file.close() + gif_file = compress_file file.close() gif_file.seek(0) return gif_file